Exemplo n.º 1
0
    def test_simulator_graphics_view(self):
        self.__setup_project()
        self.add_all_signals_to_simulator()
        stc = self.form.simulator_tab_controller  # type: SimulatorTabController
        self.assertGreater(len(stc.simulator_config.get_all_items()), 0)

        self.assertEqual(len(stc.simulator_scene.selectedItems()), 0)

        # select first message
        messages = stc.simulator_scene.get_all_message_items()
        pos = stc.ui.gvSimulator.mapFromScene(messages[0].scenePos())

        QTest.mouseClick(stc.ui.gvSimulator.viewport(), Qt.LeftButton, Qt.NoModifier, pos)

        self.assertEqual(len(stc.simulator_scene.selectedItems()), 1)
        self.assertIsInstance(stc.simulator_scene.selectedItems()[0], MessageItem)

        rules = [item for item in stc.simulator_scene.items() if isinstance(item, RuleItem)]
        self.assertEqual(len(rules), 0)
        self.menus_to_ignore = [w for w in QApplication.topLevelWidgets() if isinstance(w, QMenu)]
        timer = QTimer(self.form)
        timer.setInterval(1)
        timer.setSingleShot(True)
        timer.timeout.connect(self.__on_context_menu_simulator_graphics_view_timer_timeout)
        timer.start()

        stc.ui.gvSimulator.contextMenuEvent(QContextMenuEvent(QContextMenuEvent.Mouse, pos))

        rules = [item for item in stc.simulator_scene.items() if isinstance(item, RuleItem)]
        self.assertEqual(len(rules), 1)
Exemplo n.º 2
0
class myScrollBar(QScrollBar):
    def __init__(self, color=Qt.white, parent=None):
        QScrollBar.__init__(self, parent)
        self._color = color
        # self.setAttribute(Qt.WA_TranslucentBackground)
        self.timer = QTimer()
        self.timer.setInterval(500)
        self.timer.setSingleShot(True)
        self.timer.timeout.connect(lambda: self.parent().hideWidget(self))
        self.valueChanged.connect(lambda v: self.timer.start())
        self.valueChanged.connect(lambda: self.parent().showWidget(self))

    def setColor(self, color):
        self._color = color

    def paintEvent(self, event):
        opt = QStyleOptionSlider()
        self.initStyleOption(opt)
        style = qApp.style()
        painter = QPainter(self)

        # Background (Necessary with Qt 5.2 it seems, not with 5.4)
        # painter.save()
        # painter.setPen(Qt.NoPen)
        # painter.setBrush(self.palette().brush(QPalette.Base))
        # painter.drawRect(event.rect())
        # painter.restore()

        # slider
        r = style.subControlRect(style.CC_ScrollBar, opt, style.SC_ScrollBarSlider)
        painter.fillRect(r, self._color)
        painter.end()
Exemplo n.º 3
0
 def __init_log_writer(self):
     timer = QTimer()
     timer.setSingleShot(False)
     timer.setInterval(self.__app_log_write_interval)
     timer.timeout.connect(self.__write_app_log)
     timer.start()
     return timer
Exemplo n.º 4
0
def _process_entry_point(channel, iface_name):
    logger.info('Bus monitor process started with PID %r', os.getpid())
    app = QApplication(sys.argv)    # Inheriting args from the parent process

    def exit_if_should():
        if RUNNING_ON_WINDOWS:
            return False
        else:
            return os.getppid() != PARENT_PID       # Parent is dead

    exit_check_timer = QTimer()
    exit_check_timer.setSingleShot(False)
    exit_check_timer.timeout.connect(exit_if_should)
    exit_check_timer.start(2000)

    def get_frame():
        received, obj = channel.receive_nonblocking()
        if received:
            if obj == IPC_COMMAND_STOP:
                logger.info('Bus monitor process has received a stop request, goodbye')
                app.exit(0)
            else:
                return obj

    win = BusMonitorWindow(get_frame, iface_name)
    win.show()

    logger.info('Bus monitor process %r initialized successfully, now starting the event loop', os.getpid())
    sys.exit(app.exec_())
Exemplo n.º 5
0
class Runner(object):
    """Useful class for running jobs with a delay"""

    def __init__(self, delay=2000):
        self._timer = QTimer()
        self._timer.setSingleShot(True)
        self._timer.timeout.connect(self._execute_job)
        self._delay = delay

        self._job = None
        self._args = []
        self._kw = {}

    def cancel(self):
        """Cancel the current job"""
        self._timer.stop()
        self._job = None
        self._args = []
        self._kw = {}

    def run(self, job, *args, **kw):
        """Request a job run. If there is a job, cancel and run the new job
        with a delay specified in __init__"""
        self.cancel()
        self._job = job
        self._args = args
        self._kw = kw
        self._timer.start(self._delay)

    def _execute_job(self):
        """Execute job after the timer has timeout"""
        self._timer.stop()
        self._job(*self._args, **self._kw)
Exemplo n.º 6
0
class MemoryInfo(QObject):
    def __init__(self, parent=None):
        super(MemoryInfo, self).__init__(parent)

        self.__usage = 0
        self.load_usage()
        self.__timer = QTimer(self)
        self.__timer.setInterval(1000)
        self.__timer.setSingleShot(False)
        self.__timer.timeout.connect(self.load_usage)
        self.__timer.start()

    def load_usage(self):
        with open("/proc/self/status") as f:
            data = f.read()
        index = data.index("VmRSS:")
        split = data[index:].split(None, 3)
        self.__usage = int(split[1])
        self.usage_changed.emit()

    usage_changed = pyqtSignal(name="usageChanged")

    @pyqtProperty(int, notify=usage_changed)
    def usage(self):
        return self.__usage

    @usage.setter
    def usage(self, usage):
        if self.__usage != usage:
            self.__usage = usage
            self.usage_changed.emit()
Exemplo n.º 7
0
Arquivo: qt.py Projeto: hibtc/madgui
class Queued:

    """
    A queued trigger. Calling the trigger will invoke the handler function
    in another mainloop iteration.

    Calling the trigger multiple times before the handler was invoked (e.g.
    within the same mainloop iteration) will result in only a *single* handler
    invocation!

    This can only be used with at least a ``QCoreApplication`` instanciated.
    """

    def __init__(self, func):
        self.timer = QTimer()
        self.timer.setSingleShot(True)
        self.timer.timeout.connect(func)

    def __call__(self):
        """Schedule the handler invocation for another mainloop iteration."""
        self.timer.start()

    @classmethod
    def method(cls, func):
        """Decorator for a queued method, i.e. a method that when called,
        actually runs at a later time."""
        return property(memoize(functools.wraps(func)(
            lambda self: cls(func.__get__(self)))))
Exemplo n.º 8
0
class _StatusBar(QStatusBar):
    """Extended status bar. Supports HTML messages
    """

    def __init__(self, *args):
        QStatusBar.__init__(self, *args)
        self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Expanding)
        self.setSizeGripEnabled(False)
        self.setStyleSheet("QStatusBar {border: 0} QStatusBar::item {border: 0}")
        self._label = QLabel(self)
        self._label.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        self._label.setStyleSheet("color: red")
        self.addWidget(self._label)
        self._timer = QTimer()
        self._timer.setSingleShot(True)
        self._timer.timeout.connect(self.clearMessage)

    def term(self):
        self._timer.stop()

    def showMessage(self, text, timeout=0):
        """QStatusBar.showMessage()
        """
        self._label.setText(text)
        self._timer.stop()
        if timeout > 0:
            self._timer.start(timeout)

    def clearMessage(self):
        """QStatusBar.clearMessage()
        """
        self._label.clear()

    def currentMessage(self):
        return self._label.text()
Exemplo n.º 9
0
 def register_timer(self, time_delta, callback):
     """Registers a callback function to be run after time_delta ms."""
     timer = QTimer(self.window)
     timer.setSingleShot(True)
     timer.timeout.connect(callback)
     timer.setInterval(time_delta)
     timer.start()
Exemplo n.º 10
0
class _GlobalUpdateWordSetTimer:
    """Timer updates word set, when editor is idle. (5 sec. after last change)
    Timer is global, for avoid situation, when all instances
    update set simultaneously
    """
    _IDLE_TIMEOUT_MS = 1000

    def __init__(self):
        self._timer = QTimer()
        self._timer.setSingleShot(True)
        self._timer.timeout.connect(self._onTimer)
        self._scheduledMethods = []

    def schedule(self, method):
        if not method in self._scheduledMethods:
            self._scheduledMethods.append(method)
        self._timer.start(self._IDLE_TIMEOUT_MS)

    def cancel(self, method):
        """Cancel scheduled method
        Safe method, may be called with not-scheduled method"""
        if method in self._scheduledMethods:
            self._scheduledMethods.remove(method)

        if not self._scheduledMethods:
            self._timer.stop()

    def _onTimer(self):
        method = self._scheduledMethods.pop()
        method()
        if self._scheduledMethods:
            self._timer.start(self._IDLE_TIMEOUT_MS)
Exemplo n.º 11
0
Arquivo: base.py Projeto: gpa14/enki
def _processPendingEvents():
    """Process pending application events."""

    # Create an event loop to run in. Otherwise, we need to use the
    # QApplication main loop, which may already be running and therefore
    # unusable.
    qe = QEventLoop()

    # Create a single-shot timer. Could use QTimer.singleShot(),
    # but can't cancel this / disconnect it.
    timer = QTimer()
    timer.setSingleShot(True)
    timer.timeout.connect(qe.quit)
    timer.start(1)

    # Wait for an emitted signal.
    qe.exec_()

    # Clean up: don't allow the timer to call qe.quit after this
    # function exits, which would produce "interesting" behavior.
    timer.stop()
    # Stopping the timer may not cancel timeout signals in the
    # event queue. Disconnect the signal to be sure that loop
    # will never receive a timeout after the function exits.
    timer.timeout.disconnect(qe.quit)
Exemplo n.º 12
0
    def test_save_remove_decoding(self):
        def set_save_name():
            timer.stop()
            input_dialog = next(w for w in qApp.topLevelWidgets() if isinstance(w, QInputDialog))
            input_dialog.setTextValue("Test decoding")
            input_dialog.accept()

        def accept_delete():
            timer.stop()
            message_box = next(w for w in qApp.topLevelWidgets() if isinstance(w, QMessageBox))
            message_box.button(QMessageBox.Yes).click()

        self.dialog.ui.decoderchain.addItem(constants.DECODING_CUT)
        self.dialog.decoderchainUpdate()

        self.assertEqual(self.dialog.ui.decoderchain.count(), 1)

        timer = QTimer(self.dialog)
        timer.setSingleShot(True)
        timer.timeout.connect(set_save_name)
        timer.start(10)
        self.dialog.ui.saveas.click()

        self.assertEqual(self.dialog.ui.combobox_decodings.currentText(), "Test decoding")

        timer.timeout.disconnect(set_save_name)
        timer.timeout.connect(accept_delete)
        timer.start(10)

        self.dialog.ui.delete_decoding.click()

        self.assertNotEqual(self.dialog.ui.combobox_decodings.currentText(), "Test decoding")
Exemplo n.º 13
0
    def wait(self, time_ms, callback, onredirect=None, onerror=None):
        """
        Wait for time_ms, then run callback.

        If onredirect is True then the timer is cancelled if redirect happens.
        If onredirect is callable then in case of redirect the timer is
        cancelled and this callable is called.

        If onerror is True then the timer is cancelled if a render error
        happens. If onerror is callable then in case of a render error the
        timer is cancelled and this callable is called.
        """
        timer = QTimer()
        timer.setSingleShot(True)
        timer_callback = functools.partial(self._on_wait_timeout,
            timer=timer,
            callback=callback,
        )
        timer.timeout.connect(timer_callback)

        self.logger.log("waiting %sms; timer %s" % (time_ms, id(timer)), min_level=2)

        timer.start(time_ms)
        self._active_timers.add(timer)
        if onredirect:
            self._timers_to_cancel_on_redirect[timer] = onredirect
        if onerror:
            self._timers_to_cancel_on_error[timer] = onerror
Exemplo n.º 14
0
class Clipboard(QObject):
    changed = pyqtSignal(dict)

    def __init__(self):
        QObject.__init__(self)
        clipboard = QGuiApplication.clipboard()
        clipboard.changed.connect(self.onClipboardChanged)

        self.clipboardTimer = QTimer()
        self.clipboardTimer.setInterval(500)
        self.clipboardTimer.setSingleShot(True)
        self.clipboardTimer.timeout.connect(self.onClipboardChangedAfterDelay)

        self.selectionTimer = QTimer()
        self.selectionTimer.setInterval(1000)
        self.selectionTimer.setSingleShot(True)
        self.selectionTimer.timeout.connect(self.onSelectionChangedAfterDelay)

        self.formats = set([mimeText])

    def setFormats(self, formats):
        self.formats = set(formats)

    def onClipboardChanged(self, mode):
        if mode == QClipboard.Clipboard:
            if not QGuiApplication.clipboard().ownsClipboard():
                self.clipboardTimer.start()
            else:
                self.clipboardTimer.stop()
        elif mode == QClipboard.Selection:
            if not QGuiApplication.clipboard().ownsSelection():
                self.selectionTimer.start()
            else:
                self.selectionTimer.stop()

    def onClipboardChangedAfterDelay(self):
        self.emitChanged(QClipboard.Selection)

    def onSelectionChangedAfterDelay(self):
        self.emitChanged(QClipboard.Selection)

    def emitChanged(self, mode):
        clipboard = QGuiApplication.clipboard()
        mimeData = clipboard.mimeData()
        data = {}
        for format in self.formats:
            if mimeData.hasFormat(format):
                data[format] = mimeData.data(format)
        self.changed.emit(data)

    @pyqtProperty(str)
    def text(self):
        clipboard = QGuiApplication.clipboard()
        return clipboard.text()

    @text.setter
    def text(self, text):
        clipboard = QGuiApplication.clipboard()
        return clipboard.setText(text)
Exemplo n.º 15
0
Arquivo: qt.py Projeto: P4ncake/weboob
    def repeat(self, interval, function, *args):
        timer = QTimer()
        timer.setSingleShot(False)

        self.params[timer] = (interval, function, args)

        timer.start(0)
        timer.timeout.connect(self.timeout, Qt.QueuedConnection)
Exemplo n.º 16
0
Arquivo: qt.py Projeto: P4ncake/weboob
    def schedule(self, interval, function, *args):
        timer = QTimer()
        timer.setInterval(interval * 1000)
        timer.setSingleShot(True)

        self.params[timer] = (None, function, args)

        timer.timeout.connect(self.timeout)
        timer.start()
Exemplo n.º 17
0
class MultiBuildPlateModel(ListModel):

    maxBuildPlateChanged = pyqtSignal()
    activeBuildPlateChanged = pyqtSignal()
    selectionChanged = pyqtSignal()

    def __init__(self, parent = None):
        super().__init__(parent)

        self._update_timer = QTimer()
        self._update_timer.setInterval(100)
        self._update_timer.setSingleShot(True)
        self._update_timer.timeout.connect(self._updateSelectedObjectBuildPlateNumbers)

        self._application = Application.getInstance()
        self._application.getController().getScene().sceneChanged.connect(self._updateSelectedObjectBuildPlateNumbersDelayed)
        Selection.selectionChanged.connect(self._updateSelectedObjectBuildPlateNumbers)

        self._max_build_plate = 1  # default
        self._active_build_plate = -1

    def setMaxBuildPlate(self, max_build_plate):
        if self._max_build_plate != max_build_plate:
            self._max_build_plate = max_build_plate
            self.maxBuildPlateChanged.emit()

    ##  Return the highest build plate number
    @pyqtProperty(int, notify = maxBuildPlateChanged)
    def maxBuildPlate(self):
        return self._max_build_plate

    def setActiveBuildPlate(self, nr):
        if self._active_build_plate != nr:
            self._active_build_plate = nr
            self.activeBuildPlateChanged.emit()

    @pyqtProperty(int, notify = activeBuildPlateChanged)
    def activeBuildPlate(self):
        return self._active_build_plate

    def _updateSelectedObjectBuildPlateNumbersDelayed(self, *args):
        if not isinstance(args[0], Camera):
            self._update_timer.start()

    def _updateSelectedObjectBuildPlateNumbers(self, *args):
        result = set()
        for node in Selection.getAllSelectedObjects():
            result.add(node.callDecoration("getBuildPlateNumber"))
        self._selection_build_plates = list(result)
        self.selectionChanged.emit()

    @pyqtProperty("QVariantList", notify = selectionChanged)
    def selectionBuildPlates(self):
        return self._selection_build_plates
Exemplo n.º 18
0
def collapse(callback):
    timer = QTimer()
    timer.setSingleShot(True)
    timer.setInterval(200)
    def out(*pargs, **kwargs):
        out.pargs = pargs
        out.kwargs = kwargs
        timer.start()
    out.stop = lambda: timer.stop()
    timer.timeout.connect(lambda: callback(*out.pargs, **out.kwargs))
    return out
Exemplo n.º 19
0
    def connect(self):
        config.UID_LED_STRIP_BRICKLET = None
        self.setup.label_led_strip_found.setText('No')
        self.setup.label_led_strip_uid.setText('None')

        config.UID_MULTI_TOUCH_BRICKLET = None
        self.setup.label_multi_touch_found.setText('No')
        self.setup.label_multi_touch_uid.setText('None')

        config.UID_DUAL_BUTTON_BRICKLET = (None, None)
        self.setup.label_dual_button1_found.setText('No')
        self.setup.label_dual_button1_uid.setText('None')
        self.setup.label_dual_button2_found.setText('No')
        self.setup.label_dual_button2_uid.setText('None')

        config.UID_PIEZO_SPEAKER_BRICKLET = None
        self.setup.label_piezo_speaker_found.setText('No')
        self.setup.label_piezo_speaker_uid.setText('None')

        config.UID_SEGMENT_DISPLAY_4X7_BRICKLET = None
        self.setup.label_segment_display_found.setText('No')
        self.setup.label_segment_display_uid.setText('None')

        if self.ipcon != None:
            try:
                self.ipcon.disconnect()
            except:
                pass

        self.ipcon = IPConnection()

        host = self.setup.edit_host.text()
        port = self.setup.spinbox_port.value()
        try:
            self.ipcon.connect(host, port)
        except Error as e:
            self.error_msg.showMessage('Connection Error: ' + str(e.description) + "<br><br>Brickd installed and running?")
            return
        except socket.error as e:
            self.error_msg.showMessage('Socket error: ' + str(e) + "<br><br>Brickd installed and running?")
            return

        self.ipcon.register_callback(IPConnection.CALLBACK_ENUMERATE,
                                     self.cb_enumerate)
        self.ipcon.register_callback(IPConnection.CALLBACK_CONNECTED,
                                     self.cb_connected)

        # Wait for a second to give user visual feedback
        timer = QTimer(self)
        timer.setSingleShot(True)
        timer.timeout.connect(self.ipcon.enumerate)
        timer.start(250)
Exemplo n.º 20
0
    def __init__(self, stickMan, keyReceiver):
        self.m_stickMan = stickMan
        self.m_keyReceiver = keyReceiver

        # Create animation group to be used for all transitions.
        self.m_animationGroup = QParallelAnimationGroup()
        stickManNodeCount = self.m_stickMan.nodeCount()
        self._pas = []
        for i in range(stickManNodeCount):
            pa = QPropertyAnimation(self.m_stickMan.node(i), b'pos')
            self._pas.append(pa)
            self.m_animationGroup.addAnimation(pa)

        # Set up intial state graph.
        self.m_machine = QStateMachine()
        self.m_machine.addDefaultAnimation(self.m_animationGroup)

        self.m_alive = QState(self.m_machine)
        self.m_alive.setObjectName('alive')

        # Make it blink when lightning strikes before entering dead animation.
        lightningBlink = QState(self.m_machine)
        lightningBlink.assignProperty(self.m_stickMan.scene(),
                'backgroundBrush', Qt.white)
        lightningBlink.assignProperty(self.m_stickMan, 'penColor', Qt.black)
        lightningBlink.assignProperty(self.m_stickMan, 'fillColor', Qt.white)
        lightningBlink.assignProperty(self.m_stickMan, 'isDead', True)

        timer = QTimer(lightningBlink)
        timer.setSingleShot(True)
        timer.setInterval(100)
        lightningBlink.entered.connect(timer.start)
        lightningBlink.exited.connect(timer.stop)

        self.m_dead = QState(self.m_machine)
        self.m_dead.assignProperty(self.m_stickMan.scene(), 'backgroundBrush',
                Qt.black)
        self.m_dead.assignProperty(self.m_stickMan, 'penColor', Qt.white)
        self.m_dead.assignProperty(self.m_stickMan, 'fillColor', Qt.black)
        self.m_dead.setObjectName('dead')

        # Idle state (sets no properties).
        self.m_idle = QState(self.m_alive)
        self.m_idle.setObjectName('idle')

        self.m_alive.setInitialState(self.m_idle)

        # Lightning strikes at random.
        self.m_alive.addTransition(LightningStrikesTransition(lightningBlink))
        lightningBlink.addTransition(timer.timeout, self.m_dead)

        self.m_machine.setInitialState(self.m_alive)
Exemplo n.º 21
0
class AutoSave:
    def __init__(self, application):
        self._application = application
        self._application.getPreferences().preferenceChanged.connect(self._triggerTimer)

        self._global_stack = None

        self._application.getPreferences().addPreference("cura/autosave_delay", 1000 * 10)

        self._change_timer = QTimer()
        self._change_timer.setInterval(int(self._application.getPreferences().getValue("cura/autosave_delay")))
        self._change_timer.setSingleShot(True)

        self._enabled = True
        self._saving = False

    def initialize(self):
        # only initialise if the application is created and has started
        self._change_timer.timeout.connect(self._onTimeout)
        self._application.globalContainerStackChanged.connect(self._onGlobalStackChanged)
        self._onGlobalStackChanged()
        self._triggerTimer()

    def _triggerTimer(self, *args):
        if not self._saving:
            self._change_timer.start()

    def setEnabled(self, enabled: bool) -> None:
        self._enabled = enabled
        if self._enabled:
            self._change_timer.start()
        else:
            self._change_timer.stop()

    def _onGlobalStackChanged(self):
        if self._global_stack:
            self._global_stack.propertyChanged.disconnect(self._triggerTimer)
            self._global_stack.containersChanged.disconnect(self._triggerTimer)

        self._global_stack = self._application.getGlobalContainerStack()

        if self._global_stack:
            self._global_stack.propertyChanged.connect(self._triggerTimer)
            self._global_stack.containersChanged.connect(self._triggerTimer)

    def _onTimeout(self):
        self._saving = True # To prevent the save process from triggering another autosave.
        Logger.log("d", "Autosaving preferences, instances and profiles")

        self._application.saveSettings()

        self._saving = False
Exemplo n.º 22
0
 def _set_reply_timeout(self, reply, timeout_ms):
     request_id = self._get_request_id(reply.request())
     # reply is used as a parent for the timer in order to destroy
     # the timer when reply is destroyed. It segfaults otherwise.
     timer = QTimer(reply)
     timer.setSingleShot(True)
     timer_callback = functools.partial(self._on_reply_timeout,
                                        reply=reply,
                                        timer=timer,
                                        request_id=request_id)
     timer.timeout.connect(timer_callback)
     self._reply_timeout_timers[request_id] = timer
     timer.start(timeout_ms)
Exemplo n.º 23
0
def main():
    app = QApplication(sys.argv)
    socket = QUdpSocket(app)

    def lambdatimeout():
        socket.writeDatagram(QByteArray(b""), QHostAddress("127.0.0.1"), 14555)

    timer = QTimer(app)
    timer.timeout.connect(lambdatimeout)
    timer.setSingleShot(False)
    timer.start(1000)
    # socket.connectToHost(QHostAddress('127.0.0.1'), 14550)
    sys.exit(app.exec())
Exemplo n.º 24
0
    def __init__(self, args):
        super().__init__(args)

        self.error_msg = QErrorMessage()
        self.ipcon = IPConnection()

        signal.signal(signal.SIGINT, self.exit_demo)
        signal.signal(signal.SIGTERM, self.exit_demo)

        timer = QTimer(self)
        timer.setSingleShot(True)
        timer.timeout.connect(self.connect)
        timer.start(1)
Exemplo n.º 25
0
    def enable_asyncore_loop(self):
        """Hooks our asyncore loop into Qt's event queue."""
        def beat():
            asyncore.loop(count=1, timeout=0)

        # Yep, this isn't especially real-time IO, but it's fine for what we do.
        timer = QTimer()
        timer.timeout.connect(beat)
        timer.setSingleShot(False)
        timer.setInterval(15)
        timer.start()

        self._timer = timer
Exemplo n.º 26
0
class MyGlWidget(QGLWidget):
    "PySideApp uses Qt library to create an opengl context, listen to keyboard events, and clean up"

    def __init__(self, renderer, glformat, app):
        "Creates an OpenGL context and a window, and acquires OpenGL resources"
        super(MyGlWidget, self).__init__(glformat)
        self.renderer = renderer
        self.app = app
        # Use a timer to rerender as fast as possible
        self.timer = QTimer(self)
        self.timer.setSingleShot(True)
        self.timer.setInterval(0)
        self.timer.timeout.connect(self.render_vr)
        # Accept keyboard events
        self.setFocusPolicy(Qt.StrongFocus)

    def __enter__(self):
        "setup for RAII using 'with' keyword"
        return self

    def __exit__(self, type_arg, value, traceback):
        "cleanup for RAII using 'with' keyword"
        self.dispose_gl()

    def initializeGL(self):
        if self.renderer is not None:
            self.renderer.init_gl()
        self.timer.start()

    def paintGL(self):
        "render scene one time"
        self.renderer.render_scene()
        self.swapBuffers() # Seems OK even in single-buffer mode
        
    def render_vr(self):
        self.makeCurrent()
        self.paintGL()
        self.doneCurrent()
        self.timer.start() # render again real soon now

    def disposeGL(self):
        if self.renderer is not None:
            self.makeCurrent()
            self.renderer.dispose_gl()
            self.doneCurrent()

    def keyPressEvent(self, event):
        "press ESCAPE to quit the application"
        key = event.key()
        if key == Qt.Key_Escape:
            self.app.quit()
Exemplo n.º 27
0
class gameWidget(QWidget):
    updateScore = pyqtSignal(int, name='updateScore')
    updateCurrentScore = pyqtSignal(int, name='updateCurrentScore')

    started = pyqtSignal()
    finished = pyqtSignal()

    def __init__(self, cfg):
        super(gameWidget, self).__init__()

        self.cfg = cfg
        self.totalScore = 0
        self.curScore = 0
        self.questionsAsked = 0
        self.runState = 0
        self.correct = 0
        self.wrong = 0

        self.timer = QTimer()
        self.timer.setInterval(500)
        self.timer.setSingleShot(False)

        self.startTime = time.time()

        self.timer.timeout.connect(self.updateTimer)
        
    def updateTimer(self):
        dt = time.time() - self.startTime
        if self.cfg['level'] < 4:
            dt = 0
        if dt > self.cfg['maxTime']:
            self.timer.stop()
        curScore = max(self.cfg['minPoints'], int((self.cfg['maxTime']-dt)*self.cfg['pointsPerSec']))
        self.updateCurrentScore.emit(curScore)

    def startGame(self):
    	raise RuntimeError('Game objects must implement start')

    def start(self):
        startScore = self.cfg['pointsPerSec']*self.cfg['maxTime']
        self.updateCurrentScore.emit(startScore)
        self.started.emit()
        self.startGame()

    def finishGame(self):
        raise RuntimeError('Game objects must implement finishGame')

    def finish(self):
        self.timer.stop()
        self.finished.emit()
        self.finishGame()
Exemplo n.º 28
0
class AutoSave(Extension):
    def __init__(self):
        super().__init__()

        #Preferences.getInstance().preferenceChanged.connect(self._onPreferenceChanged)
        Preferences.getInstance().preferenceChanged.connect(self._triggerTimer)

        machine_manager = Application.getInstance().getMachineManager()

        self._profile = None
        machine_manager.activeProfileChanged.connect(self._onActiveProfileChanged)
        machine_manager.profileNameChanged.connect(self._triggerTimer)
        machine_manager.profilesChanged.connect(self._triggerTimer)
        machine_manager.machineInstanceNameChanged.connect(self._triggerTimer)
        machine_manager.machineInstancesChanged.connect(self._triggerTimer)
        self._onActiveProfileChanged()

        Preferences.getInstance().addPreference("cura/autosave_delay", 1000 * 10)

        self._change_timer = QTimer()
        self._change_timer.setInterval(Preferences.getInstance().getValue("cura/autosave_delay"))
        self._change_timer.setSingleShot(True)
        self._change_timer.timeout.connect(self._onTimeout)

        self._saving = False

    def _triggerTimer(self, *args):
        if not self._saving:
            self._change_timer.start()

    def _onActiveProfileChanged(self):
        if self._profile:
            self._profile.settingValueChanged.disconnect(self._triggerTimer)

        self._profile = Application.getInstance().getMachineManager().getWorkingProfile()

        if self._profile:
            self._profile.settingValueChanged.connect(self._triggerTimer)

    def _onTimeout(self):
        self._saving = True # To prevent the save process from triggering another autosave.
        Logger.log("d", "Autosaving preferences, instances and profiles")

        Preferences.getInstance().writeToFile(Resources.getStoragePath(Resources.Preferences, Application.getInstance().getApplicationName() + ".cfg"))
        Application.getInstance().getMachineManager().saveMachineInstances()
        Application.getInstance().getMachineManager().saveProfiles()

        self._saving = False
Exemplo n.º 29
0
class MainWindow(QMainWindow):
    def __init__(self, icon, node, iface_name):
        # Parent
        super(MainWindow, self).__init__()
        self.setWindowTitle('UAVCAN GUI Tool')
        self.setWindowIcon(icon)

        self._icon = icon
        self._node = node

        self._node_spin_timer = QTimer(self)
        self._node_spin_timer.timeout.connect(self._spin_node)
        self._node_spin_timer.setSingleShot(False)

        self._node_spin_timer.start(1)

        self._node_monitor_widget = NodeMonitorWidget(self, node)
        self._local_node_widget = LocalNodeWidget(self, node)
        self._log_message_widget = LogMessageDisplayWidget(self, node)
        self._bus_monitor_widget = BusMonitorWidget(self, node, iface_name)
        self._dynamic_node_id_allocation_widget = DynamicNodeIDAllocatorWidget(self, node,
                                                                               self._node_monitor_widget.monitor)

        outer_hbox = QHBoxLayout(self)

        left_vbox = QVBoxLayout(self)
        left_vbox.addWidget(self._local_node_widget)
        left_vbox.addWidget(self._node_monitor_widget)
        left_vbox.addWidget(self._log_message_widget)

        right_vbox = QVBoxLayout(self)
        right_vbox.addWidget(self._bus_monitor_widget, 1)
        right_vbox.addWidget(self._dynamic_node_id_allocation_widget)

        outer_hbox.addLayout(left_vbox, 1)
        outer_hbox.addLayout(right_vbox, 1)

        outer_widget = QWidget(self)
        outer_widget.setLayout(outer_hbox)
        self.setCentralWidget(outer_widget)

    def _spin_node(self):
        # We're running the node in the GUI thread.
        # This is not great, but at the moment seems like other options are even worse.
        try:
            self._node.spin(0)
        except Exception as ex:
            logger.error('Node spin error: %r', ex, exc_info=True)
Exemplo n.º 30
0
Arquivo: app.py Projeto: willingc/mu
def run():
    """
    Creates all the top-level assets for the application, sets things up and
    then runs the application. Specific tasks include:

    - set up logging
    - create an application object
    - create an editor window and status bar
    - display a splash screen while starting
    - close the splash screen after startup timer ends
    """
    setup_logging()
    logging.info('\n\n-----------------\n\nStarting Mu {}'.format(__version__))
    logging.info(platform.uname())
    logging.info('Python path: {}'.format(sys.path))

    # The app object is the application running on your computer.
    app = QApplication(sys.argv)
    app.setAttribute(Qt.AA_DontShowIconsInMenus)

    # Create the "window" we'll be looking at.
    editor_window = Window()
    # Create the "editor" that'll control the "window".
    editor = Editor(view=editor_window)
    editor.setup(setup_modes(editor, editor_window))
    # Setup the window.
    editor_window.closeEvent = editor.quit
    editor_window.setup(editor.debug_toggle_breakpoint, editor.theme)
    # Restore the previous session along with files passed by the os
    editor.restore_session(sys.argv[1:])
    # Connect the various UI elements in the window to the editor.
    editor_window.connect_tab_rename(editor.rename_tab, 'Ctrl+Shift+S')
    status_bar = editor_window.status_bar
    status_bar.connect_logs(editor.show_admin, 'Ctrl+Shift+D')

    # Display a friendly "splash" icon.
    splash = QSplashScreen(load_pixmap('splash-screen'))
    splash.show()

    # Finished starting up the application, so hide the splash icon.
    splash_be_gone = QTimer()
    splash_be_gone.timeout.connect(lambda: splash.finish(editor_window))
    splash_be_gone.setSingleShot(True)
    splash_be_gone.start(5000)

    # Stop the program after the application finishes executing.
    sys.exit(app.exec_())
Exemplo n.º 31
0
class RingWindow(QMainWindow):
    log = logging.getLogger('gui.RingWindow')
    image: RingImageQLabel
    statusbar: QStatusBar

    def __init__(self, dna_ch=None, sig_ch=None):
        super(RingWindow, self).__init__()
        path = os.path.join(sys.path[0], *__package__.split('.'))

        uic.loadUi(os.path.join(path, 'gui_ring.ui'), self)

        self.ctrl = QWidget()
        uic.loadUi(os.path.join(path, 'gui_ring_controls.ui'), self.ctrl)

        self.ctrl.zSpin.valueChanged.connect(self.onZValueChange)
        self.ctrl.openButton.pressed.connect(self.onOpenButton)
        self.ctrl.addButton.pressed.connect(self.onAddButton)
        self.ctrl.plotButton.pressed.connect(self.onPlotButton)
        self.ctrl.measureButton.pressed.connect(self.onMeasureButton)
        self.ctrl.dnaSpin.valueChanged.connect(self.onDnaValChange)
        self.ctrl.actSpin.valueChanged.connect(self.onActValChange)
        self.ctrl.dnaChk.toggled.connect(self.onImgToggle)
        self.ctrl.actChk.toggled.connect(self.onImgToggle)
        self.ctrl.renderChk.stateChanged.connect(self.onRenderChk)

        self.image.clicked.connect(self.onImgUpdate)
        self.image.lineUpdated.connect(self.onImgUpdate)
        self.image.linePicked.connect(self.onLinePickedFromImage)
        self.image.nucleusPicked.connect(self.onNucleusPickedFromImage)

        self.grph = GraphWidget()
        self.grphtimer = QTimer()
        self.grphtimer.setSingleShot(True)

        # self.stk = StkRingWidget(self.image, linePicked=self.onLinePickedFromStackGraph)

        self.grph.linePicked.connect(self.onLinePickedFromGraph)
        # self.stk.linePicked.connect(self.onLinePickedFromStackGraph)
        self.grphtimer.timeout.connect(self._graph)

        if dna_ch is not None:
            self.ctrl.dnaSpin.setValue(int(dna_ch))
        if sig_ch is not None:
            self.ctrl.actSpin.setValue(int(sig_ch))

        self.measure_n = 0
        self.selectedLine = None
        self.line_length = 4

        self.currMeasurement = None
        self.currN = None
        self.currZ = None

        self.df = pd.DataFrame()
        self.file = "/Users/Fabio/data/lab/airyscan/nil.czi"

        self.show()
        self.grph.show()
        # self.stk.show()
        self.ctrl.show()
        self.move(0, 0)
        self.resizeEvent(None)
        self.moveEvent(None)

    def resizeEvent(self, event):
        # this is a hack to resize everything when the user resizes the main window
        self.grph.setFixedWidth(self.width())
        self.image.setFixedWidth(self.width())
        self.image.setFixedHeight(self.height())
        self.image.resizeEvent(None)
        self.moveEvent(None)

    def moveEvent(self, QMoveEvent):
        self.ctrl.move(self.frameGeometry().topRight())
        self.grph.move(self.geometry().bottomLeft())
        # self.stk.move(self.ctrl.frameGeometry().topRight())

    def closeEvent(self, event):
        self._saveCurrentFileMeasurements()
        # if not self.df.empty:
        #     self.df.loc[:, "condition"] = self.ctrl.experimentLineEdit.text()
        #     self.df.loc[:, "l"] = self.df.loc[:, "l"].apply(lambda v: np.array2string(v, separator=','))
        #     self.df.to_csv(os.path.join(os.path.dirname(self.image.file), "ringlines.csv"))
        self.grph.close()
        self.ctrl.close()
        # self.stk.close()

    def focusInEvent(self, QFocusEvent):
        self.log.debug('focusInEvent')
        self.ctrl.activateWindow()
        self.grph.activateWindow()
        # self.stk.focusInEvent(None)

    def showEvent(self, event):
        self.setFocus()

    def _saveCurrentFileMeasurements(self):
        if not self.df.empty:
            self.log.info("Saving measurements.")
            fname = os.path.basename(self.image.file)
            df = self.df[self.df["file"] == fname]
            df.loc[:, "condition"] = self.ctrl.experimentLineEdit.text()
            df.loc[:, "l"] = self.df.loc[:, "l"].apply(
                lambda v: np.array2string(v, separator=','))
            df.to_csv(
                os.path.join(os.path.dirname(self.image.file), f"{fname}.csv"))

    def _graphTendency(self):
        df = pd.DataFrame(self.image.measurements).drop(
            ['x', 'y', 'c', 'ls0', 'ls1', 'd', 'sum'], axis=1)
        df.loc[:, "xx"] = df.loc[:, "l"].apply(lambda v: np.arange(
            start=0, stop=len(v) * self.image.dl, step=self.image.dl))
        df = m.vector_column_to_long_fmt(df, val_col="l", ix_col="xx")
        sns.lineplot(x="xx",
                     y="l",
                     data=df,
                     ax=self.grph.ax,
                     color='k',
                     ci="sd",
                     zorder=20)
        self.grph.ax.set_ylabel('')
        self.grph.ax.set_xlabel('')
        self.grph.canvas.draw()

    def _graph(self, alpha=1.0):
        self.grph.clear()
        lines = self.image.lines(self.image.currNucleusId)
        if lines.empty:
            return

        for ix, me in lines.iterrows():
            x = np.arange(start=0,
                          stop=len(me['value']) * self.image.dl,
                          step=self.image.dl)
            lw = 0.1 if me['li'] != self.image.selectedLine else 0.5
            self.grph.ax.plot(x,
                              me['value'],
                              linewidth=lw,
                              linestyle='-',
                              color=me['c'],
                              alpha=alpha,
                              zorder=10,
                              picker=5,
                              label=int(
                                  me['li']))  # , marker='o', markersize=1)
        self.grph.format_ax()
        # self.statusbar.showMessage("ptp: %s" % ["%d " % me['d'] for me in self.image.lines().iterrows()])
        self.grph.canvas.draw()

    @QtCore.pyqtSlot()
    def onImgToggle(self):
        self.log.debug('onImgToggle')
        if self.ctrl.dnaChk.isChecked():
            self.image.activeCh = "dna"
        if self.ctrl.actChk.isChecked():
            self.image.activeCh = "act"

    @QtCore.pyqtSlot()
    def onRenderChk(self):
        self.log.debug('onRenderChk')
        self.image.renderMeasurements = self.ctrl.renderChk.isChecked()
        # self.stk.renderMeasurements = self.ctrl.renderChk.isChecked()

    @QtCore.pyqtSlot()
    def onOpenButton(self):
        self.log.debug('onOpenButton')

        # save current file measurements as a backup
        self._saveCurrentFileMeasurements()

        qfd = QFileDialog()
        path = os.path.dirname(self.file)
        if self.image.file is not None:
            self.statusbar.showMessage("current file: %s" %
                                       os.path.basename(self.image.file))
        flt = "zeiss(*.czi)"
        f = QFileDialog.getOpenFileName(qfd, "Open File", path, flt)
        if len(f) > 0:
            self._open(f[0])

    def _open(self, fname):
        assert type(fname) is str and len(fname) > 0, "No filename given!"
        self.file = fname
        self.image.file = fname
        self.image.zstack = self.ctrl.zSpin.value()
        self.image.dnaChannel = self.ctrl.dnaSpin.value()
        self.ctrl.nchLbl.setText("%d channels" % self.image.nChannels)
        self.ctrl.nzsLbl.setText("%d z-stacks" % self.image.nZstack)
        self.ctrl.nfrLbl.setText(
            "%d %s" % (self.image.nFrames,
                       "frames" if self.image.nFrames > 1 else "frame"))
        self.currMeasurement = None
        self.currN = None
        self.currZ = None

        # self.stk.close()
        # self.stk = StkRingWidget(self.image,
        #                          nucleus_id=self.image.currNucleusId,
        #                          linePicked=self.onLinePickedFromStackGraph,
        #                          line_length=self.line_length,
        #                          dl=self.image.dl,
        #                          lines_to_measure=self.image._nlin
        #                          )
        # # self.stk.linePicked.connect(self.onLinePickedFromStackGraph)
        # self.stk.loadImages(self.image.images, xy=(100, 100), wh=(200, 200))
        # self.stk.hide()
        # self.stk.show()
        self.moveEvent(None)

    @QtCore.pyqtSlot()
    def onImgUpdate(self):
        # self.log.debug(f"onImgUpdate")
        self.ctrl.renderChk.setChecked(True)
        # self.stk.selectedLineId = self.image.selectedLine if self.image.selectedLine is not None else 0
        # self.log.debug(f"onImgUpdate. Selected line is {self.stk.selectedLineId}")
        # self.stk.drawMeasurements(erase_bkg=True)
        self.grphtimer.start(1000)

    @QtCore.pyqtSlot()
    def onNucleusPickedFromImage(self):
        self.log.debug('onNucleusPickedFromImage\r\n'
                       f'    current image={self.image.file}\r\n'
                       f'    current nucleus={self.image.currNucleusId}')
        # self.stk.dnaChannel = self.image.dnaChannel
        # self.stk.rngChannel = self.image.rngChannel
        # self.stk.selectedLineId = self.image.selectedLine if self.image.selectedLine is not None else 0
        # self.stk.selectedNucId = self.image.currNucleusId if self.image.currNucleusId is not None else 0

        # # test rectification code
        # tsplaprx = PlotSplineApproximation(self.image.currNucleus, self.image)
        # tsplaprx.fit_xy(ax=plt.figure(10).gca())
        # tsplaprx.fit_polygon(ax=plt.figure(11).gca())
        # tsplaprx.grid(ax=plt.figure(12).gca())
        #
        # trct = TestPiecewiseLinearRectification(tsplaprx, dl=4, n_dl=10, n_theta=100, pix_per_dl=1, pix_per_theta=1)
        # trct.plot_rectification()
        #
        # tfnrect = TestFunctionRectification(tsplaprx, pix_per_dl=1, pix_per_arclen=1)
        # tfnrect.plot_rectification()
        # plt.show(block=False)

        minx, miny, maxx, maxy = self.image.currNucleus.bounds
        r = int(max(maxx - minx, maxy - miny) / 2)
        # self.stk.loadImages(self.image.images, xy=[n[0] for n in self.image.currNucleus.centroid.xy],
        #                     wh=(r * self.image.pix_per_um, r * self.image.pix_per_um))
        # self.stk.measure()
        # self.stk.drawMeasurements(erase_bkg=True)

    @QtCore.pyqtSlot()
    def onMeasureButton(self):
        self.log.debug('onMeasureButton')
        self.image.paint_measures()
        self._graph(alpha=0.2)
        # self._graphTendency()

    @QtCore.pyqtSlot()
    def onZValueChange(self):
        self.log.debug('onZValueChange')
        self.image.zstack = self.ctrl.zSpin.value() % self.image.nZstack
        self.ctrl.zSpin.setValue(self.image.zstack)
        self._graph()

    @QtCore.pyqtSlot()
    def onDnaValChange(self):
        self.log.debug('onDnaValChange')
        if not self.image.nChannels > 0:
            self.log.warning(
                'Not a valid number of channels (image not loaded?).')
            return

        val = self.ctrl.dnaSpin.value() % self.image.nChannels
        self.ctrl.dnaSpin.setValue(val)
        self.image.dnaChannel = val
        if self.ctrl.dnaChk.isChecked():
            self.image.activeCh = "dna"
        self.ctrl.dnaChk.setChecked(True)

    @QtCore.pyqtSlot()
    def onActValChange(self):
        self.log.debug('onActValChange')
        if not self.image.nChannels > 0:
            self.log.warning(
                'Not a valid number of channels (image not loaded?).')
            return

        val = self.ctrl.actSpin.value() % self.image.nChannels
        self.ctrl.actSpin.setValue(val)
        self.image.rngChannel = val
        if self.ctrl.actChk.isChecked():
            self.image.activeCh = "act"
        self.ctrl.actChk.setChecked(True)

    @QtCore.pyqtSlot()
    def onAddButton(self):
        self.log.debug('onAddButton')
        if self.currMeasurement is not None and self.currN is not None and self.currZ is not None:
            new = pd.DataFrame(self.currMeasurement)
            new = new.loc[(new["n"] == self.currN) & (new["z"] == self.currZ)]
            new.loc[:, "m"] = self.measure_n
            new.loc[:, "file"] = os.path.basename(self.image.file)
            # new.loc[:, "x"] = new.loc[:, "l"].apply(lambda v: np.arange(start=0, stop=len(v), step=self.image.dl))
            self.df = self.df.append(new, ignore_index=True, sort=False)
            self.measure_n += 1
            self.currMeasurement = None
            self.currN = None
            self.currZ = None

            print(self.df)

    @QtCore.pyqtSlot()
    def onPlotButton(self):
        self.log.debug('onPlotButton')
        if self.image.measurements is None: return
        import matplotlib.pyplot as plt
        import seaborn as sns
        from matplotlib.gridspec import GridSpec

        plt.style.use('bmh')
        pal = sns.color_palette("Blues", n_colors=len(self.image.measurements))
        fig = plt.figure(figsize=(2, 2 * 4), dpi=300)
        gs = GridSpec(nrows=2, ncols=1, height_ratios=[4, 0.5])
        ax1 = plt.subplot(gs[0, 0])
        ax2 = plt.subplot(gs[1, 0])
        self.image.drawMeasurements(ax1, pal)

        lw = 1
        for me, c in zip(self.image.measurements, pal):
            x = np.arange(start=0,
                          stop=len(me['l']) * self.image.dl,
                          step=self.image.dl)
            ax2.plot(x,
                     me['l'],
                     linewidth=lw,
                     linestyle='-',
                     color=c,
                     alpha=1,
                     zorder=10)

        ax1.xaxis.set_major_locator(ticker.MultipleLocator(20))
        ax1.xaxis.set_minor_locator(ticker.MultipleLocator(10))
        ax1.yaxis.set_major_locator(ticker.MultipleLocator(20))
        ax1.yaxis.set_minor_locator(ticker.MultipleLocator(10))

        ax2.xaxis.set_major_locator(ticker.MultipleLocator(1))
        ax2.xaxis.set_minor_locator(ticker.MultipleLocator(0.5))
        ax2.yaxis.set_major_locator(ticker.MultipleLocator(1e4))
        ax2.yaxis.set_minor_locator(ticker.MultipleLocator(5e3))
        ax2.yaxis.set_major_formatter(EngFormatter(unit=''))

        fig.savefig(os.path.basename(self.image.file) + ".pdf")

    @QtCore.pyqtSlot()
    def onLinePickedFromGraph(self):
        self.log.debug('onLinePickedFromGraph')
        self.selectedLine = self.grph.selectedLine if self.grph.selectedLine is not None else None
        if self.selectedLine is not None:
            self.image.selectedLine = self.selectedLine

            self.currMeasurement = self.image.measurements
            self.currN = self.selectedLine
            self.currZ = self.image.zstack
            # self.stk.selectedLineId = self.image.selectedLine if self.image.selectedLine is not None else 0
            # self.stk.selectedNucId = self.image.currNucleusId if self.image.currNucleusId is not None else 0
            # self.stk.selectedZ = self.currZ

            # try:
            #     self.stk.drawMeasurements(erase_bkg=True)
            # except Exception as e:
            #     self.log.error(e)

            self.statusbar.showMessage("line %d selected" % self.selectedLine)

    @QtCore.pyqtSlot()
    def onLinePickedFromStackGraph(self):
        self.log.debug('onLinePickedFromStackGraph')
        self.selectedLine = self.stk.selectedLineId if self.stk.selectedLineId is not None else None
        if self.selectedLine is not None:
            self.currN = self.stk.selectedLineId
            self.stk.selectedLineId = self.image.selectedLine if self.image.selectedLine is not None else 0
            self.stk.selectedNucId = self.image.currNucleusId if self.image.currNucleusId is not None else 0
            self.currZ = self.stk.selectedZ

            self.statusbar.showMessage(
                f"Line {self.currN} of z-stack {self.currZ} selected.")
            self.log.info(
                f"Line {self.currN} of z-stack {self.currZ} selected.")

    @QtCore.pyqtSlot()
    def onLinePickedFromImage(self):
        self.log.debug('onLinePickedFromImage')
        self.selectedLine = self.image.selectedLine if self.image.selectedLine is not None else None
        if self.selectedLine is not None:
            self.currN = self.selectedLine
            self.currZ = self.image.zstack
            # self.stk.selectedLineId = self.image.selectedLine if self.image.selectedLine is not None else 0
            # self.stk.selectedNucId = self.image.currNucleusId if self.image.currNucleusId is not None else 0
            # self.stk.selectedZ = self.currZ

            self.statusbar.showMessage("Line %d selected" % self.selectedLine)
Exemplo n.º 32
0
class ElectrumGui:
    def __init__(self, config, daemon, plugins):
        set_language(config.get('language'))
        # Uncomment this call to verify objects are being properly
        # GC-ed when windows are closed
        #network.add_jobs([DebugMem([Abstract_Wallet, SPV, Synchronizer,
        #                            ElectrumWindow], interval=5)])
        QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads)
        if hasattr(QtCore.Qt, "AA_ShareOpenGLContexts"):
            QtCore.QCoreApplication.setAttribute(
                QtCore.Qt.AA_ShareOpenGLContexts)
        if hasattr(QGuiApplication, 'setDesktopFileName'):
            QGuiApplication.setDesktopFileName('electrum-sv.desktop')
        self.config = config
        self.daemon = daemon
        self.plugins = plugins
        self.windows = []
        self.efilter = OpenFileEventFilter(self.windows)
        self.app = QElectrumApplication(sys.argv)
        self.app.installEventFilter(self.efilter)
        # timer
        self.timer = QTimer(self.app)
        self.timer.setSingleShot(False)
        self.timer.setInterval(500)  # msec

        self.nd = None
        self.exception_hook = None
        # init tray
        self.dark_icon = self.config.get("dark_icon", False)
        self.tray = QSystemTrayIcon(self.tray_icon(), None)
        self.tray.setToolTip('ElectrumSV')
        self.tray.activated.connect(self.tray_activated)
        self.build_tray_menu()
        self.tray.show()
        self.app.new_window_signal.connect(self.start_new_window)
        run_hook('init_qt', self)
        ColorScheme.update_from_widget(QWidget())

    def build_tray_menu(self):
        # Avoid immediate GC of old menu when window closed via its action
        if self.tray.contextMenu() is None:
            m = QMenu()
            self.tray.setContextMenu(m)
        else:
            m = self.tray.contextMenu()
            m.clear()
        for window in self.windows:
            submenu = m.addMenu(window.wallet.basename())
            submenu.addAction(_("Show/Hide"), window.show_or_hide)
            submenu.addAction(_("Close"), window.close)
        m.addAction(_("Dark/Light"), self.toggle_tray_icon)
        m.addSeparator()
        m.addAction(_("Exit ElectrumSV"), self.close)
        self.tray.setContextMenu(m)

    def tray_icon(self):
        if self.dark_icon:
            return QIcon(':icons/electrumsv_dark_icon.png')
        else:
            return QIcon(':icons/electrumsv_light_icon.png')

    def toggle_tray_icon(self):
        self.dark_icon = not self.dark_icon
        self.config.set_key("dark_icon", self.dark_icon, True)
        self.tray.setIcon(self.tray_icon())

    def tray_activated(self, reason):
        if reason == QSystemTrayIcon.DoubleClick:
            if all([w.is_hidden() for w in self.windows]):
                for w in self.windows:
                    w.bring_to_top()
            else:
                for w in self.windows:
                    w.hide()

    def close(self):
        for window in self.windows:
            window.close()

    def new_window(self, path, uri=None):
        # Use a signal as can be called from daemon thread
        self.app.new_window_signal.emit(path, uri)

    def show_network_dialog(self, parent):
        if not self.daemon.network:
            parent.show_warning(_(
                'You are using ElectrumSV in offline mode; restart '
                'ElectrumSV if you want to get connected'),
                                title=_('Offline'))
            return
        if self.nd:
            self.nd.on_update()
            self.nd.show()
            self.nd.raise_()
            return
        self.nd = NetworkDialog(self.daemon.network, self.config)
        self.nd.show()

    def create_window_for_wallet(self, wallet):
        w = ElectrumWindow(self, wallet)
        self.windows.append(w)
        self.build_tray_menu()
        # FIXME: Remove in favour of the load_wallet hook
        run_hook('on_new_window', w)
        return w

    def start_new_window(self, path, uri):
        '''Raises the window for the wallet if it is open.  Otherwise
        opens the wallet and creates a new window for it.'''
        for w in self.windows:
            if w.wallet.storage.path == path:
                w.bring_to_top()
                break
        else:
            try:
                wallet = self.daemon.load_wallet(path, None)
                if not wallet:
                    storage = WalletStorage(path, manual_upgrades=True)
                    wizard = InstallWizard(self.config, self.app, self.plugins,
                                           storage)
                    try:
                        wallet = wizard.run_and_get_wallet()
                    except UserCancelled:
                        pass
                    except GoBack as e:
                        logging.error(
                            '[start_new_window] Exception caught (GoBack) %s',
                            e)
                    finally:
                        wizard.terminate()
                    if not wallet:
                        return
                    wallet.start_threads(self.daemon.network)
                    self.daemon.add_wallet(wallet)
            except BaseException as e:
                logging.exception("")
                if '2fa' in str(e):
                    d = QMessageBox(QMessageBox.Warning, _('Error'),
                                    '2FA wallets are not unsupported.')
                    d.exec_()
                else:
                    d = QMessageBox(QMessageBox.Warning, _('Error'),
                                    'Cannot load wallet:\n' + str(e))
                    d.exec_()
                return
            w = self.create_window_for_wallet(wallet)
        if uri:
            w.pay_to_URI(uri)
        w.bring_to_top()
        w.setWindowState(w.windowState() & ~QtCore.Qt.WindowMinimized
                         | QtCore.Qt.WindowActive)

        # this will activate the window
        w.activateWindow()
        return w

    def close_window(self, window):
        self.windows.remove(window)
        self.build_tray_menu()
        # save wallet path of last open window
        if not self.windows:
            self.config.save_last_wallet(window.wallet)
        run_hook('on_close_window', window)

    def maybe_choose_server(self):
        # Show network dialog if config does not exist
        if self.daemon.network and self.config.get('auto_connect') is None:
            try:
                wizard = InstallWizard(self.config, self.app, self.plugins,
                                       None)
                wizard.init_network(self.daemon.network)
                wizard.terminate()
            except Exception as e:
                if not isinstance(e, (UserCancelled, GoBack)):
                    logging.exception("")
                self.app.quit()

    def event_loop_started(self):
        if self.config.get("show_crash_reporter", default=True):
            self.exception_hook = Exception_Hook(self.app)
        self.timer.start()
        signal.signal(signal.SIGINT, lambda *args: self.app.quit())
        self.maybe_choose_server()
        self.config.open_last_wallet()
        path = self.config.get_wallet_path()
        if not self.start_new_window(path, self.config.get('url')):
            self.app.quit()

    def main(self):
        QTimer.singleShot(0, self.event_loop_started)
        self.app.exec_()
        # Shut down the timer cleanly
        self.timer.stop()
        # clipboard persistence
        # see http://www.mail-archive.com/[email protected]/msg17328.html
        event = QtCore.QEvent(QtCore.QEvent.Clipboard)
        self.app.sendEvent(self.app.clipboard(), event)
        self.tray.hide()
Exemplo n.º 33
0
class Demo(QWidget, Ui_Form):
    start_timer_signal = pyqtSignal()

    def __init__(self):
        super(Demo, self).__init__()
        self.setupUi(self)

        self.save_ui = Save_demo()
        self.code_box = CodeBox()
        self.myrecognition = Face_recognition()
        self.mylock = MyLock()
        self.activation_timer = QTimer()

        self.slot_init()

        self.current_photo = ''
        self.save_photo_flag = False
        self.stop_camera_flag = False

        self.save_button_init()
        self.unlock_button_init()
        self.delete_button_init()
        self.activation_timer_init()

        self.recognition_thread = Thread(target=self.run_face_recognition)
        self.recognition_thread.start()

    def slot_init(self):
        # 信号和槽连接
        self.save_button.clicked.connect(self.savePhoto)
        self.activation_timer.timeout.connect(self.deactivate_button)
        self.start_timer_signal.connect(self.start_timer)
        self.delete_button.clicked.connect(self.go_delete_photo)
        self.unlock_button.clicked.connect(self.unlock_the_door)
        self.code_box_button.clicked.connect(self.run_code_box)
        self.code_box.login_successfully.connect(self.login_successfully)

    def save_button_init(self):
        # 如果没有保存人,则保存按键可以点击,否则不能点击
        if self.myrecognition.is_dir_empty():
            self.save_button.setEnabled(True)
        else:
            self.save_button.setEnabled(False)

    def unlock_button_init(self):
        self.unlock_button.setEnabled(False)

    def delete_button_init(self):
        self.delete_button.setEnabled(False)

    def activation_timer_init(self):
        self.activation_timer.setSingleShot(True)

    def run_face_recognition(self):
        self.myrecognition.turn_on_camera()
        self.myrecognition.get_known_face_from_dir()
        while True:
            if not self.stop_camera_flag:
                self.myrecognition.face_recognition()
                # 检测是否有识别出的任务,有则运行按键按下
                self.count = self.myrecognition.found_known_people_number()
                if self.count != 0:
                    self.activate_button()
                    self.start_timer_signal.emit()
                # 检测是否按下保存
                if self.save_photo_flag:
                    self.photo_to_save = self.myrecognition.frame.copy()
                    self.save_photo_flag = False
                    self.stop_camera_flag = True
                self.myrecognition.add_mark()
                self.show = cv2.resize(self.myrecognition.frame,
                                       None,
                                       fx=1,
                                       fy=1,
                                       interpolation=cv2.INTER_CUBIC)
                self.show = cv2.cvtColor(self.show, cv2.COLOR_BGR2RGB)
                self.showImage = QImage(self.show.data, self.show.shape[1],
                                        self.show.shape[0],
                                        QImage.Format_RGB888)
                self.label.setPixmap(QPixmap.fromImage(self.showImage))

    # 保存图片
    def savePhoto(self):
        self.save_photo_flag = True
        while self.save_photo_flag:
            pass
        self.save_ui.image = self.photo_to_save
        self.save_ui.show_picture()
        self.save_ui.exec_()
        # 刷新已经记录的人
        self.reload_people()
        self.stop_camera_flag = False

    def reload_people(self):
        # 刷新存的人的资料
        self.myrecognition.get_known_face_from_dir()

    def start_timer(self):
        self.activation_timer.start(4000)

    def activate_button(self):
        if not self.activation_timer.isActive():
            self.save_button.setEnabled(True)
            self.delete_button.setEnabled(True)
            self.unlock_button.setEnabled(True)
            self.message.setText("认证成功")

    def deactivate_button(self):
        if self.myrecognition.is_dir_empty():
            self.save_button.setEnabled(True)
        else:
            self.save_button.setEnabled(False)
        self.delete_button.setEnabled(False)
        self.unlock_button.setEnabled(False)
        self.message.setText("欢迎^_^")

    def go_delete_photo(self):
        self.stop_camera_flag = True
        delete_page = DeletePage()
        delete_page.exec_()
        # 刷新已经记录的人
        self.reload_people()
        # 检测人是否空了,如果空了,要允许保存
        if self.myrecognition.is_dir_empty():
            self.save_button.setEnabled(True)
        self.stop_camera_flag = False

    def unlock_the_door(self):
        self.mylock.unlock()

    def run_code_box(self):
        self.code_box.exec_()

    def login_successfully(self):
        self.activate_button()
        self.start_timer_signal.emit()
Exemplo n.º 34
0
class PrinterOutputDevice(QObject, OutputDevice):

    printersChanged = pyqtSignal()
    connectionStateChanged = pyqtSignal(str)
    acceptsCommandsChanged = pyqtSignal()

    # Signal to indicate that the material of the active printer on the remote changed.
    materialIdChanged = pyqtSignal()

    # # Signal to indicate that the hotend of the active printer on the remote changed.
    hotendIdChanged = pyqtSignal()

    # Signal to indicate that the info text about the connection has changed.
    connectionTextChanged = pyqtSignal()

    # Signal to indicate that the configuration of one of the printers has changed.
    uniqueConfigurationsChanged = pyqtSignal()

    def __init__(
            self,
            device_id: str,
            connection_type: "ConnectionType" = ConnectionType.NotConnected,
            parent: QObject = None) -> None:
        super().__init__(
            device_id=device_id, parent=parent
        )  # type: ignore  # MyPy complains with the multiple inheritance

        self._printers = []  # type: List[PrinterOutputModel]
        self._unique_configurations = [
        ]  # type: List[PrinterConfigurationModel]

        self._monitor_view_qml_path = ""  # type: str
        self._monitor_component = None  # type: Optional[QObject]
        self._monitor_item = None  # type: Optional[QObject]

        self._control_view_qml_path = ""  # type: str
        self._control_component = None  # type: Optional[QObject]
        self._control_item = None  # type: Optional[QObject]

        self._accepts_commands = False  # type: bool

        self._update_timer = QTimer()  # type: QTimer
        self._update_timer.setInterval(
            2000)  # TODO; Add preference for update interval
        self._update_timer.setSingleShot(False)
        self._update_timer.timeout.connect(self._update)

        self._connection_state = ConnectionState.Closed  # type: ConnectionState
        self._connection_type = connection_type  # type: ConnectionType

        self._firmware_updater = None  # type: Optional[FirmwareUpdater]
        self._firmware_name = None  # type: Optional[str]
        self._address = ""  # type: str
        self._connection_text = ""  # type: str
        self.printersChanged.connect(self._onPrintersChanged)
        QtApplication.getInstance().getOutputDeviceManager(
        ).outputDevicesChanged.connect(self._updateUniqueConfigurations)

    @pyqtProperty(str, notify=connectionTextChanged)
    def address(self) -> str:
        return self._address

    def setConnectionText(self, connection_text):
        if self._connection_text != connection_text:
            self._connection_text = connection_text
            self.connectionTextChanged.emit()

    @pyqtProperty(str, constant=True)
    def connectionText(self) -> str:
        return self._connection_text

    def materialHotendChangedMessage(self, callback: Callable[[int],
                                                              None]) -> None:
        Logger.log(
            "w",
            "materialHotendChangedMessage needs to be implemented, returning 'Yes'"
        )
        callback(QMessageBox.Yes)

    def isConnected(self) -> bool:
        return self._connection_state != ConnectionState.Closed and self._connection_state != ConnectionState.Error

    def setConnectionState(self, connection_state: "ConnectionState") -> None:
        if self._connection_state != connection_state:
            self._connection_state = connection_state
            self.connectionStateChanged.emit(self._id)

    @pyqtProperty(int, constant=True)
    def connectionType(self) -> "ConnectionType":
        return self._connection_type

    @pyqtProperty(int, notify=connectionStateChanged)
    def connectionState(self) -> "ConnectionState":
        return self._connection_state

    def _update(self) -> None:
        pass

    def _getPrinterByKey(self, key: str) -> Optional["PrinterOutputModel"]:
        for printer in self._printers:
            if printer.key == key:
                return printer

        return None

    def requestWrite(self,
                     nodes: List["SceneNode"],
                     file_name: Optional[str] = None,
                     limit_mimetypes: bool = False,
                     file_handler: Optional["FileHandler"] = None,
                     filter_by_machine: bool = False,
                     **kwargs) -> None:
        raise NotImplementedError("requestWrite needs to be implemented")

    @pyqtProperty(QObject, notify=printersChanged)
    def activePrinter(self) -> Optional["PrinterOutputModel"]:
        if len(self._printers):
            return self._printers[0]
        return None

    @pyqtProperty("QVariantList", notify=printersChanged)
    def printers(self) -> List["PrinterOutputModel"]:
        return self._printers

    @pyqtProperty(QObject, constant=True)
    def monitorItem(self) -> QObject:
        # Note that we specifically only check if the monitor component is created.
        # It could be that it failed to actually create the qml item! If we check if the item was created, it will try to
        # create the item (and fail) every time.
        if not self._monitor_component:
            self._createMonitorViewFromQML()
        return self._monitor_item

    @pyqtProperty(QObject, constant=True)
    def controlItem(self) -> QObject:
        if not self._control_component:
            self._createControlViewFromQML()
        return self._control_item

    def _createControlViewFromQML(self) -> None:
        if not self._control_view_qml_path:
            return
        if self._control_item is None:
            self._control_item = QtApplication.getInstance(
            ).createQmlComponent(self._control_view_qml_path,
                                 {"OutputDevice": self})

    def _createMonitorViewFromQML(self) -> None:
        if not self._monitor_view_qml_path:
            return

        if self._monitor_item is None:
            self._monitor_item = QtApplication.getInstance(
            ).createQmlComponent(self._monitor_view_qml_path,
                                 {"OutputDevice": self})

    ##  Attempt to establish connection
    def connect(self) -> None:
        self.setConnectionState(ConnectionState.Connecting)
        self._update_timer.start()

    ##  Attempt to close the connection
    def close(self) -> None:
        self._update_timer.stop()
        self.setConnectionState(ConnectionState.Closed)

    ##  Ensure that close gets called when object is destroyed
    def __del__(self) -> None:
        self.close()

    @pyqtProperty(bool, notify=acceptsCommandsChanged)
    def acceptsCommands(self) -> bool:
        return self._accepts_commands

    ##  Set a flag to signal the UI that the printer is not (yet) ready to receive commands
    def _setAcceptsCommands(self, accepts_commands: bool) -> None:
        if self._accepts_commands != accepts_commands:
            self._accepts_commands = accepts_commands

            self.acceptsCommandsChanged.emit()

    # Returns the unique configurations of the printers within this output device
    @pyqtProperty("QVariantList", notify=uniqueConfigurationsChanged)
    def uniqueConfigurations(self) -> List["PrinterConfigurationModel"]:
        return self._unique_configurations

    def _updateUniqueConfigurations(self) -> None:
        all_configurations = set()
        for printer in self._printers:
            if printer.printerConfiguration is not None and printer.printerConfiguration.hasAnyMaterialLoaded(
            ):
                all_configurations.add(printer.printerConfiguration)
            all_configurations.update(printer.availableConfigurations)
        new_configurations = sorted(
            all_configurations, key=lambda config: config.printerType or "")
        if new_configurations != self._unique_configurations:
            self._unique_configurations = new_configurations
            self.uniqueConfigurationsChanged.emit()

    # Returns the unique configurations of the printers within this output device
    @pyqtProperty("QStringList", notify=uniqueConfigurationsChanged)
    def uniquePrinterTypes(self) -> List[str]:
        return list(
            sorted(
                set([
                    configuration.printerType or ""
                    for configuration in self._unique_configurations
                ])))

    def _onPrintersChanged(self) -> None:
        for printer in self._printers:
            printer.configurationChanged.connect(
                self._updateUniqueConfigurations)
            printer.availableConfigurationsChanged.connect(
                self._updateUniqueConfigurations)

        # At this point there may be non-updated configurations
        self._updateUniqueConfigurations()

    ##  Set the device firmware name
    #
    #   \param name The name of the firmware.
    def _setFirmwareName(self, name: str) -> None:
        self._firmware_name = name

    ##  Get the name of device firmware
    #
    #   This name can be used to define device type
    def getFirmwareName(self) -> Optional[str]:
        return self._firmware_name

    def getFirmwareUpdater(self) -> Optional["FirmwareUpdater"]:
        return self._firmware_updater

    @pyqtSlot(str)
    def updateFirmware(self, firmware_file: Union[str, QUrl]) -> None:
        if not self._firmware_updater:
            return

        self._firmware_updater.updateFirmware(firmware_file)
class NetworkClusterPrinterOutputDevice(
        NetworkPrinterOutputDevice.NetworkPrinterOutputDevice):
    printJobsChanged = pyqtSignal()
    printersChanged = pyqtSignal()
    selectedPrinterChanged = pyqtSignal()

    def __init__(self, key, address, properties, api_prefix, plugin_path):
        super().__init__(key, address, properties, api_prefix)
        # Store the address of the master.
        self._master_address = address
        name_property = properties.get(b"name", b"")
        if name_property:
            name = name_property.decode("utf-8")
        else:
            name = key

        self._plugin_path = plugin_path

        self.setName(name)
        description = i18n_catalog.i18nc(
            "@action:button Preceded by 'Ready to'.", "Print over network")
        self.setShortDescription(description)
        self.setDescription(description)

        self._stage = OutputStage.ready
        host_override = os.environ.get("CLUSTER_OVERRIDE_HOST", "")
        if host_override:
            Logger.log(
                "w",
                "Environment variable CLUSTER_OVERRIDE_HOST is set to [%s], cluster hosts are now set to this host",
                host_override)
            self._host = "http://" + host_override
        else:
            self._host = "http://" + address

        # is the same as in NetworkPrinterOutputDevicePlugin
        self._cluster_api_version = "1"
        self._cluster_api_prefix = "/cluster-api/v" + self._cluster_api_version + "/"
        self._api_base_uri = self._host + self._cluster_api_prefix

        self._file_name = None
        self._progress_message = None
        self._request = None
        self._reply = None

        # The main reason to keep the 'multipart' form data on the object
        # is to prevent the Python GC from claiming it too early.
        self._multipart = None

        self._print_view = None
        self._request_job = []

        self._monitor_view_qml_path = os.path.join(
            os.path.dirname(os.path.abspath(__file__)),
            "ClusterMonitorItem.qml")
        self._control_view_qml_path = os.path.join(
            os.path.dirname(os.path.abspath(__file__)),
            "ClusterControlItem.qml")

        self._print_jobs = []
        self._print_job_by_printer_uuid = {}
        self._print_job_by_uuid = {}  # Print jobs by their own uuid
        self._printers = []
        self._printers_dict = {}  # by unique_name

        self._connected_printers_type_count = []
        self._automatic_printer = {
            "unique_name": "",
            "friendly_name": "Automatic"
        }  # empty unique_name IS automatic selection
        self._selected_printer = self._automatic_printer

        self._cluster_status_update_timer = QTimer()
        self._cluster_status_update_timer.setInterval(5000)
        self._cluster_status_update_timer.setSingleShot(False)
        self._cluster_status_update_timer.timeout.connect(
            self._requestClusterStatus)

        self._can_pause = True
        self._can_abort = True
        self._can_pre_heat_bed = False
        self._cluster_size = int(properties.get(b"cluster_size", 0))

        self._cleanupRequest()

        #These are texts that are to be translated for future features.
        temporary_translation = i18n_catalog.i18n(
            "This printer is not set up to host a group of connected Ultimaker 3 printers."
        )
        temporary_translation2 = i18n_catalog.i18nc(
            "Count is number of printers.",
            "This printer is the host for a group of {count} connected Ultimaker 3 printers."
        ).format(count=3)
        temporary_translation3 = i18n_catalog.i18n(
            "{printer_name} has finished printing '{job_name}'. Please collect the print and confirm clearing the build plate."
        )  #When finished.
        temporary_translation4 = i18n_catalog.i18n(
            "{printer_name} is reserved to print '{job_name}'. Please change the printer's configuration to match the job, for it to start printing."
        )  #When configuration changed.

    @pyqtProperty(QObject, notify=selectedPrinterChanged)
    def controlItem(self):
        # TODO: Probably not the nicest way to do this. This needs to be done better at some point in time.
        if not self._control_component:
            self._createControlViewFromQML()
        name = self._selected_printer.get("friendly_name")
        if name == self._automatic_printer.get("friendly_name") or name == "":
            return self._control_item
        # Let cura use the default.
        return None

    @pyqtSlot(int, result=str)
    def getTimeCompleted(self, time_remaining):
        current_time = time.time()
        datetime_completed = datetime.datetime.fromtimestamp(current_time +
                                                             time_remaining)
        return "{hour:02d}:{minute:02d}".format(
            hour=datetime_completed.hour, minute=datetime_completed.minute)

    @pyqtSlot(int, result=str)
    def getDateCompleted(self, time_remaining):
        current_time = time.time()
        datetime_completed = datetime.datetime.fromtimestamp(current_time +
                                                             time_remaining)
        return (datetime_completed.strftime("%a %b ") +
                "{day}".format(day=datetime_completed.day)).upper()

    @pyqtProperty(int, constant=True)
    def clusterSize(self):
        return self._cluster_size

    @pyqtProperty(str, notify=selectedPrinterChanged)
    def name(self):
        # Show the name of the selected printer.
        # This is not the nicest way to do this, but changes to the Cura UI are required otherwise.
        name = self._selected_printer.get("friendly_name")
        if name != self._automatic_printer.get("friendly_name"):
            return name
        # Return name of cluster master.
        return self._properties.get(b"name", b"").decode("utf-8")

    def connect(self):
        super().connect()
        self._cluster_status_update_timer.start()

    def close(self):
        super().close()
        self._cluster_status_update_timer.stop()

    def _setJobState(self, job_state):
        if not self._selected_printer:
            return

        selected_printer_uuid = self._printers_dict[
            self._selected_printer["unique_name"]]["uuid"]
        if selected_printer_uuid not in self._print_job_by_printer_uuid:
            return

        print_job_uuid = self._print_job_by_printer_uuid[
            selected_printer_uuid]["uuid"]

        url = QUrl(self._api_base_uri + "print_jobs/" + print_job_uuid +
                   "/action")
        put_request = QNetworkRequest(url)
        put_request.setHeader(QNetworkRequest.ContentTypeHeader,
                              "application/json")
        data = '{"action": "' + job_state + '"}'
        self._manager.put(put_request, data.encode())

    def _requestClusterStatus(self):
        # TODO: Handle timeout. We probably want to know if the cluster is still reachable or not.
        url = QUrl(self._api_base_uri + "print_jobs/")
        print_jobs_request = QNetworkRequest(url)
        self._addUserAgentHeader(print_jobs_request)
        self._manager.get(print_jobs_request)
        # See _finishedPrintJobsRequest()

        url = QUrl(self._api_base_uri + "printers/")
        printers_request = QNetworkRequest(url)
        self._addUserAgentHeader(printers_request)
        self._manager.get(printers_request)
        # See _finishedPrintersRequest()

    def _finishedPrintJobsRequest(self, reply):
        try:
            json_data = json.loads(bytes(reply.readAll()).decode("utf-8"))
        except json.decoder.JSONDecodeError:
            Logger.log(
                "w",
                "Received an invalid print job state message: Not valid JSON.")
            return
        self.setPrintJobs(json_data)

    def _finishedPrintersRequest(self, reply):
        try:
            json_data = json.loads(bytes(reply.readAll()).decode("utf-8"))
        except json.decoder.JSONDecodeError:
            Logger.log(
                "w",
                "Received an invalid print job state message: Not valid JSON.")
            return
        self.setPrinters(json_data)

    def materialHotendChangedMessage(self, callback):
        pass  # Do nothing.

    def _startCameraStream(self):
        ## Request new image
        url = QUrl("http://" + self._printers_dict[
            self._selected_printer["unique_name"]]["ip_address"] +
                   ":8080/?action=stream")
        self._image_request = QNetworkRequest(url)
        self._addUserAgentHeader(self._image_request)
        self._image_reply = self._manager.get(self._image_request)
        self._image_reply.downloadProgress.connect(
            self._onStreamDownloadProgress)

    def spawnPrintView(self):
        if self._print_view is None:
            path = QUrl.fromLocalFile(
                os.path.join(self._plugin_path, "PrintWindow.qml"))
            component = QQmlComponent(Application.getInstance()._engine, path)

            self._print_context = QQmlContext(
                Application.getInstance()._engine.rootContext())
            self._print_context.setContextProperty("OutputDevice", self)
            self._print_view = component.create(self._print_context)

            if component.isError():
                Logger.log(
                    "e", " Errors creating component: \n%s",
                    "\n".join([e.toString() for e in component.errors()]))

        if self._print_view is not None:
            self._print_view.show()

    ##  Store job info, show Print view for settings
    def requestWrite(self,
                     nodes,
                     file_name=None,
                     filter_by_machine=False,
                     file_handler=None,
                     **kwargs):
        self._selected_printer = self._automatic_printer  # reset to default option
        self._request_job = [
            nodes, file_name, filter_by_machine, file_handler, kwargs
        ]

        if self._stage != OutputStage.ready:
            if self._error_message:
                self._error_message.hide()
            self._error_message = Message(
                i18n_catalog.i18nc(
                    "@info:status",
                    "Sending new jobs (temporarily) blocked, still sending the previous print job."
                ))
            self._error_message.show()
            return

        if len(self._printers) > 1:
            self.spawnPrintView()  # Ask user how to print it.
        elif len(self._printers) == 1:
            # If there is only one printer, don't bother asking.
            self.selectAutomaticPrinter()
            self.sendPrintJob()
        else:
            # Cluster has no printers, warn the user of this.
            if self._error_message:
                self._error_message.hide()
            self._error_message = Message(
                i18n_catalog.i18nc(
                    "@info:status",
                    "Unable to send new print job: this 3D printer is not (yet) set up to host a group of connected Ultimaker 3 printers."
                ))
            self._error_message.show()

    ##  Actually send the print job, called from the dialog
    #   :param: require_printer_name: name of printer, or ""
    @pyqtSlot()
    def sendPrintJob(self):
        nodes, file_name, filter_by_machine, file_handler, kwargs = self._request_job
        require_printer_name = self._selected_printer["unique_name"]

        self._send_gcode_start = time.time()
        Logger.log("d", "Sending print job [%s] to host..." % file_name)

        if self._stage != OutputStage.ready:
            Logger.log("d", "Unable to send print job as the state is %s",
                       self._stage)
            raise OutputDeviceError.DeviceBusyError()
        self._stage = OutputStage.uploading

        self._file_name = "%s.gcode.gz" % file_name
        self._showProgressMessage()

        self._request = self._buildSendPrintJobHttpRequest(
            require_printer_name)
        self._reply = self._manager.post(self._request, self._multipart)
        self._reply.uploadProgress.connect(self._onUploadProgress)
        # See _finishedPostPrintJobRequest()

    def _buildSendPrintJobHttpRequest(self, require_printer_name):
        api_url = QUrl(self._api_base_uri + "print_jobs/")
        request = QNetworkRequest(api_url)
        # Create multipart request and add the g-code.
        self._multipart = QHttpMultiPart(QHttpMultiPart.FormDataType)

        # Add gcode
        part = QHttpPart()
        part.setHeader(
            QNetworkRequest.ContentDispositionHeader,
            'form-data; name="file"; filename="%s"' % self._file_name)

        gcode = getattr(Application.getInstance().getController().getScene(),
                        "gcode_list")
        compressed_gcode = self._compressGcode(gcode)
        if compressed_gcode is None:
            return  # User aborted print, so stop trying.

        part.setBody(compressed_gcode)
        self._multipart.append(part)

        # require_printer_name "" means automatic
        if require_printer_name:
            self._multipart.append(
                self.__createKeyValueHttpPart("require_printer_name",
                                              require_printer_name))
        user_name = self.__get_username()
        if user_name is None:
            user_name = "unknown"
        self._multipart.append(
            self.__createKeyValueHttpPart("owner", user_name))

        self._addUserAgentHeader(request)
        return request

    def _compressGcode(self, gcode):
        self._compressing_print = True
        batched_line = ""
        max_chars_per_line = int(1024 * 1024 / 4)  # 1 / 4  MB

        byte_array_file_data = b""

        def _compressDataAndNotifyQt(data_to_append):
            compressed_data = gzip.compress(data_to_append.encode("utf-8"))
            QCoreApplication.processEvents(
            )  # Ensure that the GUI does not freeze.
            # Pretend that this is a response, as zipping might take a bit of time.
            self._last_response_time = time.time()
            return compressed_data

        if gcode is None:
            Logger.log("e", "Unable to find sliced gcode, returning empty.")
            return byte_array_file_data

        for line in gcode:
            if not self._compressing_print:
                self._progress_message.hide()
                return  # Stop trying to zip, abort was called.
            batched_line += line
            # if the gcode was read from a gcode file, self._gcode will be a list of all lines in that file.
            # Compressing line by line in this case is extremely slow, so we need to batch them.
            if len(batched_line) < max_chars_per_line:
                continue
            byte_array_file_data += _compressDataAndNotifyQt(batched_line)
            batched_line = ""

        # Also compress the leftovers.
        if batched_line:
            byte_array_file_data += _compressDataAndNotifyQt(batched_line)

        return byte_array_file_data

    def __createKeyValueHttpPart(self, key, value):
        metadata_part = QHttpPart()
        metadata_part.setHeader(QNetworkRequest.ContentTypeHeader,
                                'text/plain')
        metadata_part.setHeader(QNetworkRequest.ContentDispositionHeader,
                                'form-data; name="%s"' % (key))
        metadata_part.setBody(bytearray(value, "utf8"))
        return metadata_part

    def __get_username(self):
        try:
            return getpass.getuser()
        except:
            Logger.log(
                "d",
                "Could not get the system user name, returning 'unknown' instead."
            )
            return None

    def _finishedPrintJobPostRequest(self, reply):
        self._stage = OutputStage.ready
        if self._progress_message:
            self._progress_message.hide()
        self._progress_message = None
        self.writeFinished.emit(self)

        if reply.error():
            self._showRequestFailedMessage(reply)
            self.writeError.emit(self)
        else:
            self._showRequestSucceededMessage()
            self.writeSuccess.emit(self)

        self._cleanupRequest()

    def _showRequestFailedMessage(self, reply):
        if reply is not None:
            Logger.log(
                "w",
                "Unable to send print job to group {cluster_name}: {error_string} ({error})"
                .format(cluster_name=self.getName(),
                        error_string=str(reply.errorString()),
                        error=str(reply.error())))
            error_message_template = i18n_catalog.i18nc(
                "@info:status",
                "Unable to send print job to group {cluster_name}.")
            message = Message(text=error_message_template.format(
                cluster_name=self.getName()))
            message.show()

    def _showRequestSucceededMessage(self):
        confirmation_message_template = i18n_catalog.i18nc(
            "@info:status", "Sent {file_name} to group {cluster_name}.")
        file_name = os.path.basename(self._file_name).split(".")[0]
        message_text = confirmation_message_template.format(
            cluster_name=self.getName(), file_name=file_name)
        message = Message(text=message_text)
        button_text = i18n_catalog.i18nc("@action:button", "Show print jobs")
        button_tooltip = i18n_catalog.i18nc(
            "@info:tooltip", "Opens the print jobs interface in your browser.")
        message.addAction("open_browser", button_text, "globe", button_tooltip)
        message.actionTriggered.connect(self._onMessageActionTriggered)
        message.show()

    def setPrintJobs(self, print_jobs):
        #TODO: hack, last seen messes up the check, so drop it.
        for job in print_jobs:
            del job["last_seen"]
            # Strip any extensions
            job["name"] = self._removeGcodeExtension(job["name"])

        if self._print_jobs != print_jobs:
            old_print_jobs = self._print_jobs
            self._print_jobs = print_jobs

            self._notifyFinishedPrintJobs(old_print_jobs, print_jobs)

            # Yes, this is a hacky way of doing it, but it's quick and the API doesn't give the print job per printer
            # for some reason. ugh.
            self._print_job_by_printer_uuid = {}
            self._print_job_by_uuid = {}
            for print_job in print_jobs:
                if "printer_uuid" in print_job and print_job[
                        "printer_uuid"] is not None:
                    self._print_job_by_printer_uuid[
                        print_job["printer_uuid"]] = print_job
                self._print_job_by_uuid[print_job["uuid"]] = print_job
            self.printJobsChanged.emit()

    def _removeGcodeExtension(self, name):
        parts = name.split(".")
        if parts[-1].upper() == "GZ":
            parts = parts[:-1]
        if parts[-1].upper() == "GCODE":
            parts = parts[:-1]
        return ".".join(parts)

    def _notifyFinishedPrintJobs(self, old_print_jobs, new_print_jobs):
        """Notify the user when any of their print jobs have just completed.

        Arguments:

        old_print_jobs -- the previous list of print job status information as returned by the cluster REST API.
        new_print_jobs -- the current list of print job status information as returned by the cluster REST API.
        """
        if old_print_jobs is None:
            return

        username = self.__get_username()
        if username is None:
            return

        our_old_print_jobs = self.__filterOurPrintJobs(old_print_jobs)
        our_old_not_finished_print_jobs = [
            pj for pj in our_old_print_jobs if pj["status"] != "wait_cleanup"
        ]

        our_new_print_jobs = self.__filterOurPrintJobs(new_print_jobs)
        our_new_finished_print_jobs = [
            pj for pj in our_new_print_jobs if pj["status"] == "wait_cleanup"
        ]

        old_not_finished_print_job_uuids = set(
            [pj["uuid"] for pj in our_old_not_finished_print_jobs])

        for print_job in our_new_finished_print_jobs:
            if print_job["uuid"] in old_not_finished_print_job_uuids:

                printer_name = self.__getPrinterNameFromUuid(
                    print_job["printer_uuid"])
                if printer_name is None:
                    printer_name = i18n_catalog.i18nc("@info:status",
                                                      "Unknown printer")

                message_text = (i18n_catalog.i18nc(
                    "@info:status",
                    "Printer '{printer_name}' has finished printing '{job_name}'."
                ).format(printer_name=printer_name,
                         job_name=print_job["name"]))
                message = Message(text=message_text,
                                  title=i18n_catalog.i18nc(
                                      "@info:status", "Print finished"))
                Application.getInstance().showMessage(message)
                Application.getInstance().showToastMessage(
                    i18n_catalog.i18nc("@info:status", "Print finished"),
                    message_text)

    def __filterOurPrintJobs(self, print_jobs):
        username = self.__get_username()
        return [
            print_job for print_job in print_jobs
            if print_job["owner"] == username
        ]

    def __getPrinterNameFromUuid(self, printer_uuid):
        for printer in self._printers:
            if printer["uuid"] == printer_uuid:
                return printer["friendly_name"]
        return None

    def setPrinters(self, printers):
        if self._printers != printers:
            self._connected_printers_type_count = []
            printers_count = {}
            self._printers = printers
            self._printers_dict = dict(
                (p["unique_name"], p)
                for p in printers)  # for easy lookup by unique_name

            for printer in printers:
                variant = printer["machine_variant"]
                if variant in printers_count:
                    printers_count[variant] += 1
                else:
                    printers_count[variant] = 1
            for type in printers_count:
                self._connected_printers_type_count.append({
                    "machine_type":
                    type,
                    "count":
                    printers_count[type]
                })
            self.printersChanged.emit()

    @pyqtProperty("QVariantList", notify=printersChanged)
    def connectedPrintersTypeCount(self):
        return self._connected_printers_type_count

    @pyqtProperty("QVariantList", notify=printersChanged)
    def connectedPrinters(self):
        return self._printers

    @pyqtProperty(int, notify=printJobsChanged)
    def numJobsPrinting(self):
        num_jobs_printing = 0
        for job in self._print_jobs:
            if job["status"] in [
                    "printing", "wait_cleanup", "sent_to_printer", "pre_print",
                    "post_print"
            ]:
                num_jobs_printing += 1
        return num_jobs_printing

    @pyqtProperty(int, notify=printJobsChanged)
    def numJobsQueued(self):
        num_jobs_queued = 0
        for job in self._print_jobs:
            if job["status"] == "queued":
                num_jobs_queued += 1
        return num_jobs_queued

    @pyqtProperty("QVariantMap", notify=printJobsChanged)
    def printJobsByUUID(self):
        return self._print_job_by_uuid

    @pyqtProperty("QVariantMap", notify=printJobsChanged)
    def printJobsByPrinterUUID(self):
        return self._print_job_by_printer_uuid

    @pyqtProperty("QVariantList", notify=printJobsChanged)
    def printJobs(self):
        return self._print_jobs

    @pyqtProperty("QVariantList", notify=printersChanged)
    def printers(self):
        return [
            self._automatic_printer,
        ] + self._printers

    @pyqtSlot(str, str)
    def selectPrinter(self, unique_name, friendly_name):
        self.stopCamera()
        self._selected_printer = {
            "unique_name": unique_name,
            "friendly_name": friendly_name
        }
        Logger.log("d", "Selected printer: %s %s", friendly_name, unique_name)
        # TODO: Probably not the nicest way to do this. This needs to be done better at some point in time.
        if unique_name == "":
            self._address = self._master_address
        else:
            self._address = self._printers_dict[
                self._selected_printer["unique_name"]]["ip_address"]

        self.selectedPrinterChanged.emit()

    def _updateJobState(self, job_state):
        name = self._selected_printer.get("friendly_name")
        if name == "" or name == "Automatic":
            # TODO: This is now a bit hacked; If no printer is selected, don't show job state.
            if self._job_state != "":
                self._job_state = ""
                self.jobStateChanged.emit()
        else:
            if self._job_state != job_state:
                self._job_state = job_state
                self.jobStateChanged.emit()

    @pyqtSlot()
    def selectAutomaticPrinter(self):
        self.stopCamera()
        self._selected_printer = self._automatic_printer
        self.selectedPrinterChanged.emit()

    @pyqtProperty("QVariant", notify=selectedPrinterChanged)
    def selectedPrinterName(self):
        return self._selected_printer.get("unique_name", "")

    def getPrintJobsUrl(self):
        return self._host + "/print_jobs"

    def getPrintersUrl(self):
        return self._host + "/printers"

    def _showProgressMessage(self):
        progress_message_template = i18n_catalog.i18nc(
            "@info:progress",
            "Sending <filename>{file_name}</filename> to group {cluster_name}")
        file_name = os.path.basename(self._file_name).split(".")[0]
        self._progress_message = Message(
            progress_message_template.format(file_name=file_name,
                                             cluster_name=self.getName()), 0,
            False, -1)
        self._progress_message.addAction(
            "Abort", i18n_catalog.i18nc("@action:button", "Cancel"), None, "")
        self._progress_message.actionTriggered.connect(
            self._onMessageActionTriggered)
        self._progress_message.show()

    def _addUserAgentHeader(self, request):
        request.setRawHeader(b"User-agent",
                             b"CuraPrintClusterOutputDevice Plugin")

    def _cleanupRequest(self):
        self._reply = None
        self._request = None
        self._multipart = None
        self._stage = OutputStage.ready
        self._file_name = None

    def _onFinished(self, reply):
        super()._onFinished(reply)
        reply_url = reply.url().toString()
        status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
        if status_code == 500:
            Logger.log(
                "w", "Request to {url} returned a 500.".format(url=reply_url))
            return
        if reply.error() == QNetworkReply.ContentOperationNotPermittedError:
            # It was probably "/api/v1/materials" for legacy UM3
            return
        if reply.error() == QNetworkReply.ContentNotFoundError:
            # It was probably "/api/v1/print_job" for legacy UM3
            return

        if reply.operation() == QNetworkAccessManager.PostOperation:
            if self._cluster_api_prefix + "print_jobs" in reply_url:
                self._finishedPrintJobPostRequest(reply)
                return

        # We need to do this check *after* we process the post operation!
        # If the sending of g-code is cancelled by the user it will result in an error, but we do need to handle this.
        if reply.error() != QNetworkReply.NoError:
            Logger.log(
                "e",
                "After requesting [%s] we got a network error [%s]. Not processing anything...",
                reply_url, reply.error())
            return

        elif reply.operation() == QNetworkAccessManager.GetOperation:
            if self._cluster_api_prefix + "print_jobs" in reply_url:
                self._finishedPrintJobsRequest(reply)
            elif self._cluster_api_prefix + "printers" in reply_url:
                self._finishedPrintersRequest(reply)

    @pyqtSlot()
    def openPrintJobControlPanel(self):
        Logger.log("d", "Opening print job control panel...")
        QDesktopServices.openUrl(QUrl(self.getPrintJobsUrl()))

    @pyqtSlot()
    def openPrinterControlPanel(self):
        Logger.log("d", "Opening printer control panel...")
        QDesktopServices.openUrl(QUrl(self.getPrintersUrl()))

    def _onMessageActionTriggered(self, message, action):
        if action == "open_browser":
            QDesktopServices.openUrl(QUrl(self.getPrintJobsUrl()))

        if action == "Abort":
            Logger.log("d", "User aborted sending print to remote.")
            self._progress_message.hide()
            self._compressing_print = False
            self._stage = OutputStage.ready
            if self._reply:
                self._reply.abort()
                self._reply = None
            Application.getInstance().showPrintMonitor.emit(False)
Exemplo n.º 36
0
class StartDownloadDialog(DialogContainer):

    button_clicked = pyqtSignal(int)
    received_metainfo = pyqtSignal(dict)

    def __init__(self, parent, download_uri):
        DialogContainer.__init__(self, parent)

        torrent_name = download_uri
        if torrent_name.startswith('file:'):
            torrent_name = torrent_name[5:]
        elif torrent_name.startswith('magnet:'):
            torrent_name = unquote_plus(torrent_name)

        self.download_uri = download_uri
        self.has_metainfo = False
        self.metainfo_fetch_timer = None
        self.metainfo_retries = 0

        uic.loadUi(get_ui_file_path('startdownloaddialog.ui'),
                   self.dialog_widget)

        self.dialog_widget.setSizePolicy(QSizePolicy.Fixed,
                                         QSizePolicy.Expanding)

        connect(self.dialog_widget.browse_dir_button.clicked,
                self.on_browse_dir_clicked)
        connect(self.dialog_widget.cancel_button.clicked,
                lambda _: self.button_clicked.emit(0))
        connect(self.dialog_widget.download_button.clicked,
                self.on_download_clicked)
        connect(self.dialog_widget.select_all_files_button.clicked,
                self.on_all_files_selected_clicked)
        connect(self.dialog_widget.deselect_all_files_button.clicked,
                self.on_all_files_deselected_clicked)
        connect(self.dialog_widget.loading_files_label.clicked,
                self.on_reload_torrent_info)
        connect(self.dialog_widget.anon_download_checkbox.clicked,
                self.on_reload_torrent_info)

        self.dialog_widget.destination_input.setStyleSheet("""
        QComboBox {
            background-color: #444;
            border: none;
            color: #C0C0C0;
            padding: 4px;
        }
        QComboBox::drop-down {
            width: 20px;
            border: 1px solid #999;
            border-radius: 2px;
        }
        QComboBox QAbstractItemView {
            selection-background-color: #707070;
            color: #C0C0C0;
        }
        QComboBox::down-arrow {
            width: 12px;
            height: 12px;
            image: url('%s');
        }
        """ % get_image_path('down_arrow_input.png'))

        # self.dialog_widget.add_to_channel_checkbox.setStyleSheet(get_checkbox_style())
        checkbox_style = get_checkbox_style()
        for checkbox in [
                self.dialog_widget.add_to_channel_checkbox,
                self.dialog_widget.safe_seed_checkbox,
                self.dialog_widget.anon_download_checkbox,
        ]:
            checkbox.setStyleSheet(checkbox_style)

        if self.window().tribler_settings:
            # Set the most recent download locations in the QComboBox
            current_settings = get_gui_setting(self.window().gui_settings,
                                               tr("recent_download_locations"),
                                               "")
            if len(current_settings) > 0:
                recent_locations = [
                    unhexlify(url).decode('utf-8')
                    for url in current_settings.split(",")
                ]
                self.dialog_widget.destination_input.addItems(recent_locations)
            else:
                self.dialog_widget.destination_input.setCurrentText(
                    self.window().tribler_settings['download_defaults']
                    ['saveas'])

        self.dialog_widget.torrent_name_label.setText(torrent_name)

        connect(self.dialog_widget.anon_download_checkbox.stateChanged,
                self.on_anon_download_state_changed)
        self.dialog_widget.anon_download_checkbox.setChecked(
            self.window().tribler_settings['download_defaults']
            ['anonymity_enabled'])
        self.dialog_widget.safe_seed_checkbox.setChecked(
            self.window().tribler_settings['download_defaults']
            ['safeseeding_enabled'])
        self.dialog_widget.add_to_channel_checkbox.setChecked(
            self.window().tribler_settings['download_defaults']
            ['add_download_to_channel'])

        self.dialog_widget.safe_seed_checkbox.setEnabled(
            self.dialog_widget.anon_download_checkbox.isChecked())

        self.perform_files_request()
        self.dialog_widget.files_list_view.setHidden(True)
        self.dialog_widget.download_files_container.setHidden(True)
        self.dialog_widget.adjustSize()
        self.on_anon_download_state_changed(None)

        self.on_main_window_resize()

        self.rest_request = None

    def close_dialog(self, checked=False):
        if self.rest_request:
            self.rest_request.cancel_request()

        if self.metainfo_fetch_timer:
            self.metainfo_fetch_timer.stop()

        # Loading files label is a clickable label with pyqtsignal which could leak,
        # so delete the widget while closing the dialog.
        if self.dialog_widget and self.dialog_widget.loading_files_label:
            try:
                self.dialog_widget.loading_files_label.deleteLater()
            except RuntimeError:
                logging.debug(
                    "Deleting loading files widget in the dialog widget failed."
                )

        super().close_dialog()

    def get_selected_files(self):
        included_files = []
        for ind in range(
                self.dialog_widget.files_list_view.topLevelItemCount()):
            item = self.dialog_widget.files_list_view.topLevelItem(ind)
            if item.checkState(2) == Qt.Checked:
                included_files.append(ind)

        return included_files

    def perform_files_request(self):
        if self.closed:
            return

        direct = not self.dialog_widget.anon_download_checkbox.isChecked()
        request = f"torrentinfo?uri={quote_plus_unicode(self.download_uri)}"
        if direct is True:
            request = request + f"&hops=0"
        self.rest_request = TriblerNetworkRequest(request,
                                                  self.on_received_metainfo,
                                                  capture_core_errors=False)

        if self.metainfo_retries <= METAINFO_MAX_RETRIES:
            fetch_mode = tr("directly") if direct else tr("anonymously")
            loading_message = tr("Loading torrent files %s...") % fetch_mode
            timeout_message = tr(
                "Timeout in fetching files %s. Retrying  %i/%i") % (
                    fetch_mode,
                    self.metainfo_retries,
                    METAINFO_MAX_RETRIES,
                )

            self.dialog_widget.loading_files_label.setText(
                loading_message
                if not self.metainfo_retries else timeout_message)
            self.metainfo_fetch_timer = QTimer()
            connect(self.metainfo_fetch_timer.timeout,
                    self.perform_files_request)
            self.metainfo_fetch_timer.setSingleShot(True)
            self.metainfo_fetch_timer.start(METAINFO_TIMEOUT)

            self.metainfo_retries += 1

    def on_received_metainfo(self, response):
        if not response or not self or self.closed:
            return

        if 'error' in response:
            if response['error'] == 'metainfo error':
                # If it failed to load metainfo for max number of times, show an error message in red.
                if self.metainfo_retries > METAINFO_MAX_RETRIES:
                    self.dialog_widget.loading_files_label.setStyleSheet(
                        "color:#ff0000;")
                    self.dialog_widget.loading_files_label.setText(
                        tr("Failed to load files. Click to retry again."))
                    return
                self.perform_files_request()

            elif 'code' in response['error'] and response['error'][
                    'code'] == 'IOError':
                self.dialog_widget.loading_files_label.setText(
                    tr("Unable to read torrent file data"))
            else:
                self.dialog_widget.loading_files_label.setText(
                    tr("Error: %s") % response['error'])
            return

        metainfo = json.loads(unhexlify(response['metainfo']))
        if 'files' in metainfo['info']:  # Multi-file torrent
            files = metainfo['info']['files']
        else:
            files = [{
                'path': [metainfo['info']['name']],
                'length': metainfo['info']['length']
            }]

        # Show if the torrent already exists in the downloads
        if response.get('download_exists'):
            self.dialog_widget.existing_download_info_label.setText(
                tr("Note: this torrent already exists in the Downloads"))
        else:
            self.dialog_widget.existing_download_info_label.setText("")

        self.dialog_widget.files_list_view.clear()
        for filename in files:
            item = DownloadFileTreeWidgetItem(
                self.dialog_widget.files_list_view)
            item.setText(0, '/'.join(filename['path']))
            item.setText(1, format_size(float(filename['length'])))
            item.setData(0, Qt.UserRole, filename)
            item.setCheckState(2, Qt.Checked)
            self.dialog_widget.files_list_view.addTopLevelItem(item)

        self.has_metainfo = True
        self.dialog_widget.loading_files_label.setHidden(True)
        self.dialog_widget.download_files_container.setHidden(False)
        self.dialog_widget.files_list_view.setHidden(False)
        self.dialog_widget.adjustSize()
        self.on_main_window_resize()

        self.received_metainfo.emit(metainfo)

    def on_reload_torrent_info(self, *args):
        """
        This method is called when user clicks the QLabel text showing loading or error message. Here, we reset
        the number of retries to fetch the metainfo. Note color of QLabel is also reset to white.
        """
        self.dialog_widget.loading_files_label.setStyleSheet("color:#ffffff;")
        self.metainfo_retries = 0
        self.perform_files_request()

    def on_browse_dir_clicked(self, checked):
        chosen_dir = QFileDialog.getExistingDirectory(
            self.window(),
            tr("Please select the destination directory of your download"), "",
            QFileDialog.ShowDirsOnly)

        if len(chosen_dir) != 0:
            self.dialog_widget.destination_input.setCurrentText(chosen_dir)

            is_writable, error = is_dir_writable(chosen_dir)
            if not is_writable:
                gui_error_message = tr(
                    "Tribler cannot download to <i>%s</i> directory. Please add proper write "
                    "permissions to the directory or choose another download directory. [%s]"
                ) % (chosen_dir, error)
                ConfirmationDialog.show_message(self.dialog_widget,
                                                tr("Insufficient Permissions"),
                                                gui_error_message, "OK")

    def on_anon_download_state_changed(self, _):
        if self.dialog_widget.anon_download_checkbox.isChecked():
            self.dialog_widget.safe_seed_checkbox.setChecked(True)
        self.dialog_widget.safe_seed_checkbox.setEnabled(
            not self.dialog_widget.anon_download_checkbox.isChecked())

    def on_download_clicked(self, checked):
        if self.has_metainfo and len(self.get_selected_files()
                                     ) == 0:  # User deselected all torrents
            ConfirmationDialog.show_error(
                self.window(), tr("No files selected"),
                tr("Please select at least one file to download."))
        else:
            download_dir = self.dialog_widget.destination_input.currentText()
            is_writable, error = is_dir_writable(download_dir)
            if not is_writable:
                gui_error_message = tr(
                    "Tribler cannot download to <i>%s</i> directory. Please add proper write "
                    "permissions to the directory or choose another download directory and try "
                    "to download again. [%s]") % (download_dir, error)
                ConfirmationDialog.show_message(self.dialog_widget,
                                                tr("Insufficient Permissions"),
                                                gui_error_message, "OK")
            else:
                self.button_clicked.emit(1)

    def on_all_files_selected_clicked(self, checked):
        for ind in range(
                self.dialog_widget.files_list_view.topLevelItemCount()):
            item = self.dialog_widget.files_list_view.topLevelItem(ind)
            item.setCheckState(2, Qt.Checked)

    def on_all_files_deselected_clicked(self, checked):
        for ind in range(
                self.dialog_widget.files_list_view.topLevelItemCount()):
            item = self.dialog_widget.files_list_view.topLevelItem(ind)
            item.setCheckState(2, Qt.Unchecked)
Exemplo n.º 37
0
class SnapWidget(QWidget, Ui_SnapWidget):
    """
    Class implementing the snapshot widget.
    """
    ModeFullscreen = 0
    ModeScreen = 1
    ModeRectangle = 2
    ModeFreehand = 3
    ModeEllipse = 4

    def __init__(self, parent=None):
        """
        Constructor
        
        @param parent reference to the parent widget (QWidget)
        """
        super(SnapWidget, self).__init__(parent)
        self.setupUi(self)

        self.saveButton.setIcon(UI.PixmapCache.getIcon("fileSaveAs.png"))
        self.takeButton.setIcon(UI.PixmapCache.getIcon("cameraPhoto.png"))
        self.copyButton.setIcon(UI.PixmapCache.getIcon("editCopy.png"))
        self.copyPreviewButton.setIcon(UI.PixmapCache.getIcon("editCopy.png"))
        self.setWindowIcon(UI.PixmapCache.getIcon("ericSnap.png"))

        self.modeCombo.addItem(self.tr("Fullscreen"),
                               SnapWidget.ModeFullscreen)
        self.modeCombo.addItem(self.tr("Rectangular Selection"),
                               SnapWidget.ModeRectangle)
        self.modeCombo.addItem(self.tr("Ellipical Selection"),
                               SnapWidget.ModeEllipse)
        self.modeCombo.addItem(self.tr("Freehand Selection"),
                               SnapWidget.ModeFreehand)
        if QApplication.desktop().screenCount() > 1:
            self.modeCombo.addItem(self.tr("Current Screen"),
                                   SnapWidget.ModeScreen)
        self.__mode = int(Preferences.Prefs.settings.value("Snapshot/Mode", 0))
        index = self.modeCombo.findData(self.__mode)
        if index == -1:
            index = 0
        self.modeCombo.setCurrentIndex(index)

        self.__delay = int(
            Preferences.Prefs.settings.value("Snapshot/Delay", 0))
        self.delaySpin.setValue(self.__delay)

        if PYQT_VERSION_STR >= "5.0.0":
            from PyQt5.QtCore import QStandardPaths
            picturesLocation = QStandardPaths.writableLocation(
                QStandardPaths.PicturesLocation)
        else:
            from PyQt5.QtGui import QDesktopServices
            picturesLocation = QDesktopServices.storageLocation(
                QDesktopServices.PicturesLocation)
        self.__filename = Preferences.Prefs.settings.value(
            "Snapshot/Filename",
            os.path.join(picturesLocation,
                         self.tr("snapshot") + "1.png"))

        self.__grabber = None
        self.__snapshot = QPixmap()
        self.__savedPosition = QPoint()
        self.__modified = False

        self.__grabberWidget = QWidget(None, Qt.X11BypassWindowManagerHint)
        self.__grabberWidget.move(-10000, -10000)
        self.__grabberWidget.installEventFilter(self)

        self.__initFileFilters()

        self.__initShortcuts()

        self.preview.startDrag.connect(self.__dragSnapshot)

        from .SnapshotTimer import SnapshotTimer
        self.__grabTimer = SnapshotTimer()
        self.__grabTimer.timeout.connect(self.__grabTimerTimeout)
        self.__updateTimer = QTimer()
        self.__updateTimer.setSingleShot(True)
        self.__updateTimer.timeout.connect(self.__updatePreview)

        self.__updateCaption()
        self.takeButton.setFocus()

    def __initFileFilters(self):
        """
        Private method to define the supported image file filters.
        """
        filters = {
            'bmp': self.tr("Windows Bitmap File (*.bmp)"),
            'gif': self.tr("Graphic Interchange Format File (*.gif)"),
            'ico': self.tr("Windows Icon File (*.ico)"),
            'jpg': self.tr("JPEG File (*.jpg)"),
            'mng': self.tr("Multiple-Image Network Graphics File (*.mng)"),
            'pbm': self.tr("Portable Bitmap File (*.pbm)"),
            'pcx': self.tr("Paintbrush Bitmap File (*.pcx)"),
            'pgm': self.tr("Portable Graymap File (*.pgm)"),
            'png': self.tr("Portable Network Graphics File (*.png)"),
            'ppm': self.tr("Portable Pixmap File (*.ppm)"),
            'sgi': self.tr("Silicon Graphics Image File (*.sgi)"),
            'svg': self.tr("Scalable Vector Graphics File (*.svg)"),
            'tga': self.tr("Targa Graphic File (*.tga)"),
            'tif': self.tr("TIFF File (*.tif)"),
            'xbm': self.tr("X11 Bitmap File (*.xbm)"),
            'xpm': self.tr("X11 Pixmap File (*.xpm)"),
        }

        outputFormats = []
        writeFormats = QImageWriter.supportedImageFormats()
        for writeFormat in writeFormats:
            try:
                outputFormats.append(filters[bytes(writeFormat).decode()])
            except KeyError:
                pass
        outputFormats.sort()
        self.__outputFilter = ';;'.join(outputFormats)

        self.__defaultFilter = filters['png']

    def __initShortcuts(self):
        """
        Private method to initialize the keyboard shortcuts.
        """
        self.__quitShortcut = QShortcut(QKeySequence(QKeySequence.Quit), self,
                                        self.close)

        self.__copyShortcut = QShortcut(QKeySequence(QKeySequence.Copy), self,
                                        self.copyButton.animateClick)

        self.__quickSaveShortcut = QShortcut(QKeySequence(Qt.Key_Q), self,
                                             self.__quickSave)

        self.__save1Shortcut = QShortcut(QKeySequence(QKeySequence.Save), self,
                                         self.saveButton.animateClick)
        self.__save2Shortcut = QShortcut(QKeySequence(Qt.Key_S), self,
                                         self.saveButton.animateClick)

        self.__grab1Shortcut = QShortcut(QKeySequence(QKeySequence.New), self,
                                         self.takeButton.animateClick)
        self.__grab2Shortcut = QShortcut(QKeySequence(Qt.Key_N), self,
                                         self.takeButton.animateClick)
        self.__grab3Shortcut = QShortcut(QKeySequence(Qt.Key_Space), self,
                                         self.takeButton.animateClick)

    def __quickSave(self):
        """
        Private slot to save the snapshot bypassing the file selection dialog.
        """
        if not self.__snapshot.isNull():
            while os.path.exists(self.__filename):
                self.__autoIncFilename()

            if self.__saveImage(self.__filename):
                self.__modified = False
                self.__autoIncFilename()
                self.__updateCaption()

    @pyqtSlot()
    def on_saveButton_clicked(self):
        """
        Private slot to save the snapshot.
        """
        if not self.__snapshot.isNull():
            while os.path.exists(self.__filename):
                self.__autoIncFilename()

            fileName, selectedFilter = E5FileDialog.getSaveFileNameAndFilter(
                self, self.tr("Save Snapshot"), self.__filename,
                self.__outputFilter, self.__defaultFilter,
                E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite))
            if not fileName:
                return

            ext = QFileInfo(fileName).suffix()
            if not ext:
                ex = selectedFilter.split("(*")[1].split(")")[0]
                if ex:
                    fileName += ex

            if self.__saveImage(fileName):
                self.__modified = False
                self.__filename = fileName
                self.__autoIncFilename()
                self.__updateCaption()

    def __saveImage(self, fileName):
        """
        Private method to save the snapshot.
        
        @param fileName name of the file to save to (string)
        @return flag indicating success (boolean)
        """
        if QFileInfo(fileName).exists():
            res = E5MessageBox.yesNo(
                self,
                self.tr("Save Snapshot"),
                self.tr("<p>The file <b>{0}</b> already exists."
                        " Overwrite it?</p>").format(fileName),
                icon=E5MessageBox.Warning)
            if not res:
                return False

        file = QFile(fileName)
        if not file.open(QFile.WriteOnly):
            E5MessageBox.warning(
                self, self.tr("Save Snapshot"),
                self.tr("Cannot write file '{0}:\n{1}.").format(
                    fileName, file.errorString()))
            return False

        ok = self.__snapshot.save(file)
        file.close()

        if not ok:
            E5MessageBox.warning(
                self, self.tr("Save Snapshot"),
                self.tr("Cannot write file '{0}:\n{1}.").format(
                    fileName, file.errorString()))

        return ok

    def __autoIncFilename(self):
        """
        Private method to auto-increment the file name.
        """
        # Extract the file name
        name = os.path.basename(self.__filename)

        # If the name contains a number, then increment it.
        numSearch = QRegExp("(^|[^\\d])(\\d+)")
        # We want to match as far left as possible, and when the number is
        # at the start of the name.

        # Does it have a number?
        start = numSearch.lastIndexIn(name)
        if start != -1:
            # It has a number, increment it.
            start = numSearch.pos(2)  # Only the second group is of interest.
            numAsStr = numSearch.capturedTexts()[2]
            number = "{0:0{width}d}".format(int(numAsStr) + 1,
                                            width=len(numAsStr))
            name = name[:start] + number + name[start + len(numAsStr):]
        else:
            # no number
            start = name.rfind('.')
            if start != -1:
                # has a '.' somewhere, e.g. it has an extension
                name = name[:start] + '1' + name[start:]
            else:
                # no extension, just tack it on to the end
                name += '1'

        self.__filename = os.path.join(os.path.dirname(self.__filename), name)
        self.__updateCaption()

    @pyqtSlot()
    def on_takeButton_clicked(self):
        """
        Private slot to take a snapshot.
        """
        self.__mode = self.modeCombo.itemData(self.modeCombo.currentIndex())
        self.__delay = self.delaySpin.value()

        self.__savedPosition = self.pos()
        self.hide()

        if self.__delay:
            self.__grabTimer.start(self.__delay)
        else:
            QTimer.singleShot(200, self.__startUndelayedGrab)

    def __grabTimerTimeout(self):
        """
        Private slot to perform a delayed grab operation.
        """
        if self.__mode == SnapWidget.ModeRectangle:
            self.__grabRectangle()
        elif self.__mode == SnapWidget.ModeEllipse:
            self.__grabEllipse()
        elif self.__mode == SnapWidget.ModeFreehand:
            self.__grabFreehand()
        else:
            self.__performGrab()

    def __startUndelayedGrab(self):
        """
        Private slot to perform an undelayed grab operation.
        """
        if self.__mode == SnapWidget.ModeRectangle:
            self.__grabRectangle()
        elif self.__mode == SnapWidget.ModeEllipse:
            self.__grabEllipse()
        elif self.__mode == SnapWidget.ModeFreehand:
            self.__grabFreehand()
        else:
            if Globals.isMacPlatform():
                self.__performGrab()
            else:
                self.__grabberWidget.show()
                self.__grabberWidget.grabMouse(Qt.CrossCursor)

    def __grabRectangle(self):
        """
        Private method to grab a rectangular screen region.
        """
        from .SnapshotRegionGrabber import SnapshotRegionGrabber
        self.__grabber = SnapshotRegionGrabber(
            mode=SnapshotRegionGrabber.Rectangle)
        self.__grabber.grabbed.connect(self.__captured)

    def __grabEllipse(self):
        """
        Private method to grab an elliptical screen region.
        """
        from .SnapshotRegionGrabber import SnapshotRegionGrabber
        self.__grabber = SnapshotRegionGrabber(
            mode=SnapshotRegionGrabber.Ellipse)
        self.__grabber.grabbed.connect(self.__captured)

    def __grabFreehand(self):
        """
        Private method to grab a non-rectangular screen region.
        """
        from .SnapshotFreehandGrabber import SnapshotFreehandGrabber
        self.__grabber = SnapshotFreehandGrabber()
        self.__grabber.grabbed.connect(self.__captured)

    def __performGrab(self):
        """
        Private method to perform a screen grab other than a selected region.
        """
        self.__grabberWidget.releaseMouse()
        self.__grabberWidget.hide()
        self.__grabTimer.stop()

        if self.__mode == SnapWidget.ModeFullscreen:
            desktop = QApplication.desktop()
            if qVersion() >= "5.0.0":
                self.__snapshot = QApplication.screens()[0].grabWindow(
                    desktop.winId(), desktop.x(), desktop.y(), desktop.width(),
                    desktop.height())
            else:
                self.__snapshot = QPixmap.grabWindow(desktop.winId(),
                                                     desktop.x(), desktop.y(),
                                                     desktop.width(),
                                                     desktop.height())
        elif self.__mode == SnapWidget.ModeScreen:
            desktop = QApplication.desktop()
            screenId = desktop.screenNumber(QCursor.pos())
            geom = desktop.screenGeometry(screenId)
            x = geom.x()
            y = geom.y()
            if qVersion() >= "5.0.0":
                self.__snapshot = QApplication.screens()[0].grabWindow(
                    desktop.winId(), x, y, geom.width(), geom.height())
            else:
                self.__snapshot = QPixmap.grabWindow(desktop.winId(), x, y,
                                                     geom.width(),
                                                     geom.height())
        else:
            self.__snapshot = QPixmap()

        self.__redisplay()
        self.__modified = True
        self.__updateCaption()

    def __redisplay(self):
        """
        Private method to redisplay the window.
        """
        self.__updatePreview()
        QApplication.restoreOverrideCursor()
        if not self.__savedPosition.isNull():
            self.move(self.__savedPosition)
        self.show()
        self.raise_()

        self.saveButton.setEnabled(not self.__snapshot.isNull())
        self.copyButton.setEnabled(not self.__snapshot.isNull())
        self.copyPreviewButton.setEnabled(not self.__snapshot.isNull())

    @pyqtSlot()
    def on_copyButton_clicked(self):
        """
        Private slot to copy the snapshot to the clipboard.
        """
        if not self.__snapshot.isNull():
            QApplication.clipboard().setPixmap(QPixmap(self.__snapshot))

    @pyqtSlot()
    def on_copyPreviewButton_clicked(self):
        """
        Private slot to copy the snapshot preview to the clipboard.
        """
        QApplication.clipboard().setPixmap(self.preview.pixmap())

    def __captured(self, pixmap):
        """
        Private slot to show a preview of the snapshot.
        
        @param pixmap pixmap of the snapshot (QPixmap)
        """
        self.__grabber.close()
        self.__snapshot = QPixmap(pixmap)

        self.__grabber.grabbed.disconnect(self.__captured)
        self.__grabber = None

        self.__redisplay()
        self.__modified = True
        self.__updateCaption()

    def __updatePreview(self):
        """
        Private slot to update the preview picture.
        """
        self.preview.setToolTip(
            self.tr("Preview of the snapshot image ({0:n} x {1:n})").format(
                self.__snapshot.width(), self.__snapshot.height()))
        self.preview.setPreview(self.__snapshot)
        self.preview.adjustSize()

    def resizeEvent(self, evt):
        """
        Protected method handling a resizing of the window.
        
        @param evt resize event (QResizeEvent)
        """
        self.__updateTimer.start(200)

    def __dragSnapshot(self):
        """
        Private slot handling the dragging of the preview picture.
        """
        drag = QDrag(self)
        mimeData = QMimeData()
        mimeData.setImageData(self.__snapshot)
        drag.setMimeData(mimeData)
        drag.setPixmap(self.preview.pixmap())
        drag.exec_(Qt.CopyAction)

    def eventFilter(self, obj, evt):
        """
        Public method to handle event for other objects.
        
        @param obj reference to the object (QObject)
        @param evt reference to the event (QEvent)
        @return flag indicating that the event should be filtered out (boolean)
        """
        if obj == self.__grabberWidget and \
                evt.type() == QEvent.MouseButtonPress:
            if QWidget.mouseGrabber() != self.__grabberWidget:
                return False
            if evt.button() == Qt.LeftButton:
                self.__performGrab()

        return False

    def closeEvent(self, evt):
        """
        Protected method handling the close event.
        
        @param evt close event (QCloseEvent)
        """
        if self.__modified:
            res = E5MessageBox.question(
                self, self.tr("eric6 Snapshot"),
                self.tr("""The application contains an unsaved snapshot."""),
                E5MessageBox.StandardButtons(E5MessageBox.Abort
                                             | E5MessageBox.Discard
                                             | E5MessageBox.Save))
            if res == E5MessageBox.Abort:
                evt.ignore()
                return
            elif res == E5MessageBox.Save:
                self.on_saveButton_clicked()

        Preferences.Prefs.settings.setValue("Snapshot/Delay",
                                            self.delaySpin.value())
        Preferences.Prefs.settings.setValue(
            "Snapshot/Mode",
            self.modeCombo.itemData(self.modeCombo.currentIndex()))
        Preferences.Prefs.settings.setValue("Snapshot/Filename",
                                            self.__filename)
        Preferences.Prefs.settings.sync()

    def __updateCaption(self):
        """
        Private method to update the window caption.
        """
        self.setWindowTitle("{0}[*] - {1}".format(
            os.path.basename(self.__filename), self.tr("eric6 Snapshot")))
        self.setWindowModified(self.__modified)
        self.pathNameEdit.setText(os.path.dirname(self.__filename))
Exemplo n.º 38
0
class LayerView(View):
    def __init__(self):
        super().__init__()
        self._shader = None
        self._selection_shader = None
        self._num_layers = 0
        self._layer_percentage = 0  # what percentage of layers need to be shown (SLider gives value between 0 - 100)
        self._proxy = LayerViewProxy.LayerViewProxy()
        self._controller.getScene().getRoot().childrenChanged.connect(
            self._onSceneChanged)
        self._max_layers = 0
        self._current_layer_num = 0
        self._current_layer_mesh = None
        self._current_layer_jumps = None
        self._top_layers_job = None
        self._activity = False

        self._solid_layers = 5

        self._top_layer_timer = QTimer()
        self._top_layer_timer.setInterval(50)
        self._top_layer_timer.setSingleShot(True)
        self._top_layer_timer.timeout.connect(self._startUpdateTopLayers)

        self._busy = False

    def getActivity(self):
        return self._activity

    def getCurrentLayer(self):
        return self._current_layer_num

    def _onSceneChanged(self, node):
        self.calculateMaxLayers()

    def getMaxLayers(self):
        return self._max_layers

    busyChanged = Signal()

    def isBusy(self):
        return self._busy

    def setBusy(self, busy):
        if busy != self._busy:
            self._busy = busy
            self.busyChanged.emit()

    def resetLayerData(self):
        self._current_layer_mesh = None
        self._current_layer_jumps = None

    def beginRendering(self):
        scene = self.getController().getScene()
        renderer = self.getRenderer()

        if not self._selection_shader:
            self._selection_shader = OpenGL.getInstance().createShaderProgram(
                Resources.getPath(Resources.Shaders, "color.shader"))
            self._selection_shader.setUniformValue("u_color",
                                                   Color(32, 32, 32, 128))

        for node in DepthFirstIterator(scene.getRoot()):
            # We do not want to render ConvexHullNode as it conflicts with the bottom layers.
            # However, it is somewhat relevant when the node is selected, so do render it then.
            if type(node) is ConvexHullNode and not Selection.isSelected(
                    node.getWatchedNode()):
                continue

            if not node.render(renderer):
                if node.getMeshData() and node.isVisible():
                    if Selection.isSelected(node):
                        renderer.queueNode(node,
                                           transparent=True,
                                           shader=self._selection_shader)
                    layer_data = node.callDecoration("getLayerData")
                    if not layer_data:
                        continue

                    # Render all layers below a certain number as line mesh instead of vertices.
                    if self._current_layer_num - self._solid_layers > -1:
                        start = 0
                        end = 0
                        element_counts = layer_data.getElementCounts()
                        for layer, counts in element_counts.items():
                            if layer + self._solid_layers > self._current_layer_num:
                                break
                            end += counts

                        # This uses glDrawRangeElements internally to only draw a certain range of lines.
                        renderer.queueNode(node,
                                           mesh=layer_data,
                                           mode=RenderBatch.RenderMode.Lines,
                                           range=(start, end))

                    if self._current_layer_mesh:
                        renderer.queueNode(node, mesh=self._current_layer_mesh)

                    if self._current_layer_jumps:
                        renderer.queueNode(node,
                                           mesh=self._current_layer_jumps)

    def setLayer(self, value):
        if self._current_layer_num != value:
            self._current_layer_num = value
            if self._current_layer_num < 0:
                self._current_layer_num = 0
            if self._current_layer_num > self._max_layers:
                self._current_layer_num = self._max_layers

            self._current_layer_mesh = None
            self._current_layer_jumps = None

            self._top_layer_timer.start()

            self.currentLayerNumChanged.emit()

    currentLayerNumChanged = Signal()

    def calculateMaxLayers(self):
        scene = self.getController().getScene()
        renderer = self.getRenderer()  # TODO: @UnusedVariable
        self._activity = True

        self._old_max_layers = self._max_layers
        ## Recalculate num max layers
        new_max_layers = 0
        for node in DepthFirstIterator(scene.getRoot()):
            layer_data = node.callDecoration("getLayerData")
            if not layer_data:
                continue

            if new_max_layers < len(layer_data.getLayers()):
                new_max_layers = len(layer_data.getLayers()) - 1

        if new_max_layers > 0 and new_max_layers != self._old_max_layers:
            self._max_layers = new_max_layers

            # The qt slider has a bit of weird behavior that if the maxvalue needs to be changed first
            # if it's the largest value. If we don't do this, we can have a slider block outside of the
            # slider.
            if new_max_layers > self._current_layer_num:
                self.maxLayersChanged.emit()
                self.setLayer(int(self._max_layers))
            else:
                self.setLayer(int(self._max_layers))
                self.maxLayersChanged.emit()
        self._top_layer_timer.start()

    maxLayersChanged = Signal()
    currentLayerNumChanged = Signal()

    ##  Hackish way to ensure the proxy is already created, which ensures that the layerview.qml is already created
    #   as this caused some issues.
    def getProxy(self, engine, script_engine):
        return self._proxy

    def endRendering(self):
        pass

    def event(self, event):
        modifiers = QApplication.keyboardModifiers()
        ctrl_is_active = modifiers == Qt.ControlModifier
        if event.type == Event.KeyPressEvent and ctrl_is_active:
            if event.key == KeyEvent.UpKey:
                self.setLayer(self._current_layer_num + 1)
                return True
            if event.key == KeyEvent.DownKey:
                self.setLayer(self._current_layer_num - 1)
                return True

    def _startUpdateTopLayers(self):
        if self._top_layers_job:
            self._top_layers_job.finished.disconnect(
                self._updateCurrentLayerMesh)
            self._top_layers_job.cancel()

        self.setBusy(True)

        self._top_layers_job = _CreateTopLayersJob(self._controller.getScene(),
                                                   self._current_layer_num,
                                                   self._solid_layers)
        self._top_layers_job.finished.connect(self._updateCurrentLayerMesh)
        self._top_layers_job.start()

    def _updateCurrentLayerMesh(self, job):
        self.setBusy(False)

        if not job.getResult():
            return

        self._current_layer_mesh = job.getResult().get("layers")
        self._current_layer_jumps = job.getResult().get("jumps")
        self._controller.getScene().sceneChanged.emit(
            self._controller.getScene().getRoot())

        self._top_layers_job = None
Exemplo n.º 39
0
class Editor(CodeEditor,ComponentMixin):

    name = 'Code Editor'

    # This signal is emitted whenever the currently-open file changes and
    # autoreload is enabled.
    triggerRerender = pyqtSignal(bool)
    sigFilenameChanged = pyqtSignal(str)

    preferences = Parameter.create(name='Preferences',children=[
        {'name': 'Font size', 'type': 'int', 'value': 12},
        {'name': 'Autoreload', 'type': 'bool', 'value': False},
        {'name': 'Autoreload delay', 'type': 'int', 'value': 50},
        {'name': 'Autoreload: watch imported modules', 'type': 'bool', 'value': False},
        {'name': 'Line wrap', 'type': 'bool', 'value': False},
        {'name': 'Color scheme', 'type': 'list',
         'values': ['Spyder','Monokai','Zenburn'], 'value': 'Spyder'}])

    EXTENSIONS = 'py'

    def __init__(self,parent=None):

        self._watched_file = None

        super(Editor,self).__init__(parent)
        ComponentMixin.__init__(self)

        self.setup_editor(linenumbers=True,
                          markers=True,
                          edge_line=False,
                          tab_mode=False,
                          show_blanks=True,
                          font=QFontDatabase.systemFont(QFontDatabase.FixedFont),
                          language='Python',
                          filename='')

        self._actions =  \
                {'File' : [QAction(icon('new'),
                                  'New',
                                  self,
                                  shortcut='ctrl+N',
                                  triggered=self.new),
                          QAction(icon('open'),
                                  'Open',
                                  self,
                                  shortcut='ctrl+O',
                                  triggered=self.open),
                          QAction(icon('save'),
                                  'Save',
                                  self,
                                  shortcut='ctrl+S',
                                  triggered=self.save),
                          QAction(icon('save_as'),
                                  'Save as',
                                  self,
                                  shortcut='ctrl+shift+S',
                                  triggered=self.save_as),
                          QAction(icon('autoreload'),
                                  'Automatic reload and preview',
                                  self,triggered=self.autoreload,
                                  checkable=True,
                                  checked=False,
                                  objectName='autoreload'),
                          ]}

        for a in self._actions.values():
            self.addActions(a)


        self._fixContextMenu()

        # autoreload support
        self._file_watcher = QFileSystemWatcher(self)
        # we wait for 50ms after a file change for the file to be written completely
        self._file_watch_timer = QTimer(self)
        self._file_watch_timer.setInterval(self.preferences['Autoreload delay'])
        self._file_watch_timer.setSingleShot(True)
        self._file_watcher.fileChanged.connect(
                lambda val: self._file_watch_timer.start())
        self._file_watch_timer.timeout.connect(self._file_changed)

        self.updatePreferences()

    def _fixContextMenu(self):

        menu = self.menu

        menu.removeAction(self.run_cell_action)
        menu.removeAction(self.run_cell_and_advance_action)
        menu.removeAction(self.run_selection_action)
        menu.removeAction(self.re_run_last_cell_action)

    def updatePreferences(self,*args):

        self.set_color_scheme(self.preferences['Color scheme'])

        font = self.font()
        font.setPointSize(self.preferences['Font size'])
        self.set_font(font)

        self.findChild(QAction, 'autoreload') \
            .setChecked(self.preferences['Autoreload'])

        self._file_watch_timer.setInterval(self.preferences['Autoreload delay'])

        self.toggle_wrap_mode(self.preferences['Line wrap'])

        self._clear_watched_paths()
        self._watch_paths()

    def confirm_discard(self):

        if self.modified:
            rv =  confirm(self,'Please confirm','Current document is not saved - do you want to continue?')
        else:
            rv = True

        return rv

    def new(self):

        if not self.confirm_discard(): return

        self.set_text('')
        self.filename = ''
        self.reset_modified()

    def open(self):
        
        if not self.confirm_discard(): return

        curr_dir = Path(self.filename).abspath().dirname()
        fname = get_open_filename(self.EXTENSIONS, curr_dir)
        if fname != '':
            self.load_from_file(fname)

    def load_from_file(self,fname):

        self.set_text_from_file(fname)
        self.filename = fname
        self.reset_modified()

    def save(self):

        if self._filename != '':

            if self.preferences['Autoreload']:
                self._file_watcher.blockSignals(True)
                self._file_watch_timer.stop()

            with open(self._filename, 'w') as f:
                f.write(self.toPlainText())

            if self.preferences['Autoreload']:
                self._file_watcher.blockSignals(False)
                self.triggerRerender.emit(True)

            self.reset_modified()

        else:
            self.save_as()

    def save_as(self):

        fname = get_save_filename(self.EXTENSIONS)
        if fname != '':
            with open(fname,'w') as f:
                f.write(self.toPlainText())
                self.filename = fname

            self.reset_modified()

    def _update_filewatcher(self):
        if self._watched_file and (self._watched_file != self.filename or not self.preferences['Autoreload']):
            self._clear_watched_paths()
            self._watched_file = None
        if self.preferences['Autoreload'] and self.filename and self.filename != self._watched_file:
            self._watched_file = self._filename
            self._watch_paths()

    @property
    def filename(self):
        return self._filename

    @filename.setter
    def filename(self, fname):
        self._filename = fname
        self._update_filewatcher()
        self.sigFilenameChanged.emit(fname)

    def _clear_watched_paths(self):
        paths = self._file_watcher.files()
        if paths:
            self._file_watcher.removePaths(paths)

    def _watch_paths(self):
        if self._filename:
            self._file_watcher.addPath(self._filename)
            if self.preferences['Autoreload: watch imported modules']:
                self._file_watcher.addPaths(get_imported_module_paths(self._filename))

    # callback triggered by QFileSystemWatcher
    def _file_changed(self):
        # neovim writes a file by removing it first so must re-add each time
        self._watch_paths()
        self.set_text_from_file(self._filename)
        self.triggerRerender.emit(True)

    # Turn autoreload on/off.
    def autoreload(self, enabled):
        self.preferences['Autoreload'] = enabled
        self._update_filewatcher()

    def reset_modified(self):

        self.document().setModified(False)
        
    @property
    def modified(self):
        
        return self.document().isModified()

    def saveComponentState(self,store):

        if self.filename != '':
            store.setValue(self.name+'/state',self.filename)

    def restoreComponentState(self,store):

        filename = store.value(self.name+'/state',self.filename)

        if filename and filename != '':
            try:
                self.load_from_file(filename)
            except IOError:
                self._logger.warning(f'could not open {filename}')
Exemplo n.º 40
0
class revisions(QWidget, Ui_revisions):
    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.setupUi(self)
        self.splitter.setStretchFactor(0, 5)
        self.splitter.setStretchFactor(1, 70)

        self.listDelegate = listCompleterDelegate(self)
        self.list.setItemDelegate(self.listDelegate)
        self.list.itemClicked.connect(self.showDiff)
        self.list.setContextMenuPolicy(Qt.CustomContextMenu)
        self.list.customContextMenuRequested.connect(self.popupMenu)
        self.btnDelete.setEnabled(False)
        self.btnDelete.clicked.connect(self.delete)
        self.btnRestore.clicked.connect(self.restore)
        self.btnRestore.setEnabled(False)

        # self.list.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)

        self.updateTimer = QTimer()
        self.updateTimer.setSingleShot(True)
        self.updateTimer.setInterval(500)
        self.updateTimer.timeout.connect(self.update)
        self.updateTimer.stop()

        self.menu = QMenu(self)
        self.actGroup = QActionGroup(self)

        self.actShowDiff = QAction(self.tr("Show modifications"), self.menu)
        self.actShowDiff.setCheckable(True)
        self.actShowDiff.setChecked(True)
        self.actShowDiff.triggered.connect(self.showDiff)
        self.menu.addAction(self.actShowDiff)
        self.actGroup.addAction(self.actShowDiff)

        self.actShowVersion = QAction(self.tr("Show ancient version"),
                                      self.menu)
        self.actShowVersion.setCheckable(True)
        self.actShowVersion.setChecked(False)
        self.actShowVersion.triggered.connect(self.showDiff)
        self.menu.addAction(self.actShowVersion)
        self.actGroup.addAction(self.actShowVersion)

        self.menu.addSeparator()
        self.actShowSpaces = QAction(self.tr("Show spaces"), self.menu)
        self.actShowSpaces.setCheckable(True)
        self.actShowSpaces.setChecked(False)
        self.actShowSpaces.triggered.connect(self.showDiff)
        self.menu.addAction(self.actShowSpaces)

        self.actDiffOnly = QAction(self.tr("Show modifications only"),
                                   self.menu)
        self.actDiffOnly.setCheckable(True)
        self.actDiffOnly.setChecked(True)
        self.actDiffOnly.triggered.connect(self.showDiff)
        self.menu.addAction(self.actDiffOnly)
        self.btnOptions.setMenu(self.menu)

        self._model = None
        self._index = None

    def setModel(self, model):
        self._model = model
        self._model.dataChanged.connect(self.updateMaybe)

    def setCurrentModelIndex(self, index):
        self._index = index
        self.view.setText("")
        self.update()

    def updateMaybe(self, topLeft, bottomRight):
        if self._index and \
                                topLeft.column() <= Outline.revisions <= bottomRight.column() and \
                                topLeft.row() <= self._index.row() <= bottomRight.row():
            # self.update()
            self.updateTimer.start()

    def update(self):
        self.list.clear()
        item = self._index.internalPointer()
        rev = item.revisions()
        # Sort revisions
        rev = sorted(rev, key=lambda x: x[0], reverse=True)
        for r in rev:
            timestamp = datetime.datetime.fromtimestamp(
                r[0]).strftime('%Y-%m-%d %H:%M:%S')
            readable = self.readableDelta(r[0])
            i = QListWidgetItem(readable)
            i.setData(Qt.UserRole, r[0])
            i.setData(Qt.UserRole + 1, timestamp)
            self.list.addItem(i)

    def readableDelta(self, timestamp):
        now = datetime.datetime.now()
        delta = now - datetime.datetime.fromtimestamp(timestamp)
        if delta.days > 365:
            return self.tr("{} years ago").format(str(int(delta.days / 365)))
        elif delta.days > 30:
            return self.tr("{} months ago").format(str(int(delta.days / 30.5)))
        elif delta.days > 0:
            return self.tr("{} days ago").format(str(delta.days))
        if delta.days == 1:
            return self.tr("1 day ago")
        elif delta.seconds > 60 * 60:
            return self.tr("{} hours ago").format(
                str(int(delta.seconds / 60 / 60)))
        elif delta.seconds > 60:
            return self.tr("{} minutes ago").format(
                str(int(delta.seconds / 60)))
        else:
            return self.tr("{} seconds ago").format(str(delta.seconds))

    def showDiff(self):
        # UI stuff
        self.actShowSpaces.setEnabled(self.actShowDiff.isChecked())
        self.actDiffOnly.setEnabled(self.actShowDiff.isChecked())

        # FIXME: Errors in line number
        i = self.list.currentItem()

        if not i:
            self.btnDelete.setEnabled(False)
            self.btnRestore.setEnabled(False)
            return

        self.btnDelete.setEnabled(True)
        self.btnRestore.setEnabled(True)

        ts = i.data(Qt.UserRole)
        item = self._index.internalPointer()

        textNow = item.text()
        textBefore = [r[1] for r in item.revisions() if r[0] == ts][0]

        if self.actShowVersion.isChecked():
            self.view.setText(textBefore)
            return

        textNow = textNow.splitlines()
        textBefore = textBefore.splitlines()

        d = difflib.Differ()
        diff = list(d.compare(textBefore, textNow))

        if self.actShowSpaces.isChecked():
            _format = lambda x: x.replace(" ", "␣ ")
        else:
            _format = lambda x: x

        extra = "<br>"
        diff = [d for d in diff if d and not d[:2] == "? "]
        mydiff = ""
        skip = False
        for n, l in enumerate(diff):
            l = diff[n]
            op = l[:2]
            txt = l[2:]
            op2 = diff[n + 1][:2] if n + 1 < len(diff) else None
            txt2 = diff[n + 1][2:] if n + 1 < len(diff) else None

            if skip:
                skip = False
                continue

            # Same line
            if op == "  " and not self.actDiffOnly.isChecked():
                mydiff += "{}{}".format(txt, extra)

            elif op == "- " and op2 == "+ ":
                if self.actDiffOnly.isChecked():
                    mydiff += "<br><span style='color: blue;'>{}</span><br>".format(
                        self.tr("Line {}:").format(str(n)))
                s = difflib.SequenceMatcher(None, txt, txt2, autojunk=True)
                newline = ""
                for tag, i1, i2, j1, j2 in s.get_opcodes():
                    if tag == "equal":
                        newline += txt[i1:i2]
                    elif tag == "delete":
                        newline += "<span style='color:red; background:yellow;'>{}</span>".format(
                            _format(txt[i1:i2]))
                    elif tag == "insert":
                        newline += "<span style='color:green; background:yellow;'>{}</span>".format(
                            _format(txt2[j1:j2]))
                    elif tag == "replace":
                        newline += "<span style='color:red; background:yellow;'>{}</span>".format(
                            _format(txt[i1:i2]))
                        newline += "<span style='color:green; background:yellow;'>{}</span>".format(
                            _format(txt2[j1:j2]))

                # Few ugly tweaks for html diffs
                newline = re.sub(
                    r"(<span style='color.*?><span.*?>)</span>(.*)<span style='color:.*?>(</span></span>)",
                    "\\1\\2\\3", newline)
                newline = re.sub(
                    r"<p align=\"<span style='color:red; background:yellow;'>cen</span><span style='color:green; background:yellow;'>righ</span>t<span style='color:red; background:yellow;'>er</span>\" style=\" -qt-block-indent:0; -qt-user-state:0; \">(.*?)</p>",
                    "<p align=\"right\"><span style='color:green; background:yellow;'>\\1</span></p>",
                    newline)
                newline = re.sub(
                    r"<p align=\"<span style='color:green; background:yellow;'>cente</span>r<span style='color:red; background:yellow;'>ight</span>\" style=\" -qt-block-indent:0; -qt-user-state:0; \">(.*)</p>",
                    "<p align=\"center\"><span style='color:green; background:yellow;'>\\1</span></p>",
                    newline)
                newline = re.sub(r"<p(<span.*?>)(.*?)(</span>)(.*?)>(.*?)</p>",
                                 "<p\\2\\4>\\1\\5\\3</p>", newline)

                mydiff += newline + extra
                skip = True
            elif op == "- ":
                if self.actDiffOnly.isChecked():
                    mydiff += "<br>{}:<br>".format(str(n))
                mydiff += "<span style='color:red;'>{}</span>{}".format(
                    txt, extra)
            elif op == "+ ":
                if self.actDiffOnly.isChecked():
                    mydiff += "<br>{}:<br>".format(str(n))
                mydiff += "<span style='color:green;'>{}</span>{}".format(
                    txt, extra)

        self.view.setText(mydiff)

    def restore(self):
        i = self.list.currentItem()
        if not i:
            return
        ts = i.data(Qt.UserRole)
        item = self._index.internalPointer()
        textBefore = [r[1] for r in item.revisions() if r[0] == ts][0]
        index = self._index.sibling(self._index.row(), Outline.text)
        self._index.model().setData(index, textBefore)
        # item.setData(Outline.text, textBefore)

    def delete(self):
        i = self.list.currentItem()
        if not i:
            return
        ts = i.data(Qt.UserRole)
        self._index.internalPointer().deleteRevision(ts)

    def clearAll(self):
        self._index.internalPointer().clearAllRevisions()

    def saveState(self):
        return [
            self.actShowDiff.isChecked(),
            self.actShowVersion.isChecked(),
            self.actShowSpaces.isChecked(),
            self.actDiffOnly.isChecked(),
        ]

    def popupMenu(self, pos):
        i = self.list.itemAt(pos)
        m = QMenu(self)
        if i:
            m.addAction(self.tr("Restore")).triggered.connect(self.restore)
            m.addAction(self.tr("Delete")).triggered.connect(self.delete)
            m.addSeparator()
        if self.list.count():
            m.addAction(self.tr("Clear all")).triggered.connect(self.clearAll)

        m.popup(self.list.mapToGlobal(pos))

    def restoreState(self, state):
        self.actShowDiff.setChecked(state[0])
        self.actShowVersion.setChecked(state[1])
        self.actShowSpaces.setChecked(state[2])
        self.actDiffOnly.setChecked(state[3])
        self.actShowSpaces.setEnabled(self.actShowDiff.isChecked())
        self.actDiffOnly.setEnabled(self.actShowDiff.isChecked())
Exemplo n.º 41
0
class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
    def __init__(self,
                 device_id,
                 address: str,
                 properties,
                 parent=None) -> None:
        super().__init__(device_id=device_id,
                         address=address,
                         properties=properties,
                         parent=parent)
        self._api_prefix = "/api/v1/"
        self._number_of_extruders = 2

        self._authentication_id = None
        self._authentication_key = None

        self._authentication_counter = 0
        self._max_authentication_counter = 5 * 60  # Number of attempts before authentication timed out (5 min)

        self._authentication_timer = QTimer()
        self._authentication_timer.setInterval(
            1000)  # TODO; Add preference for update interval
        self._authentication_timer.setSingleShot(False)

        self._authentication_timer.timeout.connect(self._onAuthenticationTimer)

        # The messages are created when connect is called the first time.
        # This ensures that the messages are only created for devices that actually want to connect.
        self._authentication_requested_message = None
        self._authentication_failed_message = None
        self._authentication_succeeded_message = None
        self._not_authenticated_message = None

        self.authenticationStateChanged.connect(
            self._onAuthenticationStateChanged)

        self.setPriority(
            3
        )  # Make sure the output device gets selected above local file output
        self.setName(self._id)
        self.setShortDescription(
            i18n_catalog.i18nc("@action:button Preceded by 'Ready to'.",
                               "Print over network"))
        self.setDescription(
            i18n_catalog.i18nc("@properties:tooltip", "Print over network"))

        self.setIconName("print")

        self._monitor_view_qml_path = os.path.join(
            os.path.dirname(os.path.abspath(__file__)), "MonitorItem.qml")

        self._output_controller = LegacyUM3PrinterOutputController(self)

    def _onAuthenticationStateChanged(self):
        # We only accept commands if we are authenticated.
        self._setAcceptsCommands(
            self._authentication_state == AuthState.Authenticated)

        if self._authentication_state == AuthState.Authenticated:
            self.setConnectionText(
                i18n_catalog.i18nc("@info:status",
                                   "Connected over the network."))
        elif self._authentication_state == AuthState.AuthenticationRequested:
            self.setConnectionText(
                i18n_catalog.i18nc(
                    "@info:status",
                    "Connected over the network. Please approve the access request on the printer."
                ))
        elif self._authentication_state == AuthState.AuthenticationDenied:
            self.setConnectionText(
                i18n_catalog.i18nc(
                    "@info:status",
                    "Connected over the network. No access to control the printer."
                ))

    def _setupMessages(self):
        self._authentication_requested_message = Message(
            i18n_catalog.i18nc(
                "@info:status",
                "Access to the printer requested. Please approve the request on the printer"
            ),
            lifetime=0,
            dismissable=False,
            progress=0,
            title=i18n_catalog.i18nc("@info:title", "Authentication status"))

        self._authentication_failed_message = Message(
            i18n_catalog.i18nc("@info:status", ""),
            title=i18n_catalog.i18nc("@info:title", "Authentication Status"))
        self._authentication_failed_message.addAction(
            "Retry", i18n_catalog.i18nc("@action:button", "Retry"), None,
            i18n_catalog.i18nc("@info:tooltip", "Re-send the access request"))
        self._authentication_failed_message.actionTriggered.connect(
            self._messageCallback)
        self._authentication_succeeded_message = Message(
            i18n_catalog.i18nc("@info:status",
                               "Access to the printer accepted"),
            title=i18n_catalog.i18nc("@info:title", "Authentication Status"))

        self._not_authenticated_message = Message(i18n_catalog.i18nc(
            "@info:status",
            "No access to print with this printer. Unable to send print job."),
                                                  title=i18n_catalog.i18nc(
                                                      "@info:title",
                                                      "Authentication Status"))
        self._not_authenticated_message.addAction(
            "Request", i18n_catalog.i18nc("@action:button", "Request Access"),
            None,
            i18n_catalog.i18nc("@info:tooltip",
                               "Send access request to the printer"))
        self._not_authenticated_message.actionTriggered.connect(
            self._messageCallback)

    def _messageCallback(self, message_id=None, action_id="Retry"):
        if action_id == "Request" or action_id == "Retry":
            if self._authentication_failed_message:
                self._authentication_failed_message.hide()
            if self._not_authenticated_message:
                self._not_authenticated_message.hide()

            self._requestAuthentication()

    def connect(self):
        super().connect()
        self._setupMessages()
        global_container = CuraApplication.getInstance(
        ).getGlobalContainerStack()
        if global_container:
            self._authentication_id = global_container.getMetaDataEntry(
                "network_authentication_id", None)
            self._authentication_key = global_container.getMetaDataEntry(
                "network_authentication_key", None)

    def close(self):
        super().close()
        if self._authentication_requested_message:
            self._authentication_requested_message.hide()
        if self._authentication_failed_message:
            self._authentication_failed_message.hide()
        if self._authentication_succeeded_message:
            self._authentication_succeeded_message.hide()
        self._sending_gcode = False
        self._compressing_gcode = False
        self._authentication_timer.stop()

    ##  Send all material profiles to the printer.
    def _sendMaterialProfiles(self):
        Logger.log("i", "Sending material profiles to printer")

        # TODO: Might want to move this to a job...
        for container in ContainerRegistry.getInstance(
        ).findInstanceContainers(type="material"):
            try:
                xml_data = container.serialize()
                if xml_data == "" or xml_data is None:
                    continue

                names = ContainerManager.getInstance().getLinkedMaterials(
                    container.getId())
                if names:
                    # There are other materials that share this GUID.
                    if not container.isReadOnly():
                        continue  # If it's not readonly, it's created by user, so skip it.

                file_name = "none.xml"

                self.postForm("materials",
                              "form-data; name=\"file\";filename=\"%s\"" %
                              file_name,
                              xml_data.encode(),
                              onFinished=None)

            except NotImplementedError:
                # If the material container is not the most "generic" one it can't be serialized an will raise a
                # NotImplementedError. We can simply ignore these.
                pass

    def requestWrite(self,
                     nodes: List[SceneNode],
                     file_name: Optional[str] = None,
                     limit_mimetypes: bool = False,
                     file_handler: Optional[FileHandler] = None,
                     **kwargs: str) -> None:
        if not self.activePrinter:
            # No active printer. Unable to write
            return

        if self.activePrinter.state not in ["idle", ""]:
            # Printer is not able to accept commands.
            return

        if self._authentication_state != AuthState.Authenticated:
            # Not authenticated, so unable to send job.
            return

        self.writeStarted.emit(self)

        gcode_dict = getattr(
            CuraApplication.getInstance().getController().getScene(),
            "gcode_dict", [])
        active_build_plate_id = CuraApplication.getInstance(
        ).getMultiBuildPlateModel().activeBuildPlate
        gcode_list = gcode_dict[active_build_plate_id]

        if not gcode_list:
            # Unable to find g-code. Nothing to send
            return

        self._gcode = gcode_list

        errors = self._checkForErrors()
        if errors:
            text = i18n_catalog.i18nc("@label",
                                      "Unable to start a new print job.")
            informative_text = i18n_catalog.i18nc(
                "@label",
                "There is an issue with the configuration of your Ultimaker, which makes it impossible to start the print. "
                "Please resolve this issues before continuing.")
            detailed_text = ""
            for error in errors:
                detailed_text += error + "\n"

            CuraApplication.getInstance().messageBox(
                i18n_catalog.i18nc("@window:title",
                                   "Mismatched configuration"),
                text,
                informative_text,
                detailed_text,
                buttons=QMessageBox.Ok,
                icon=QMessageBox.Critical,
                callback=self._messageBoxCallback)
            return  # Don't continue; Errors must block sending the job to the printer.

        # There might be multiple things wrong with the configuration. Check these before starting.
        warnings = self._checkForWarnings()

        if warnings:
            text = i18n_catalog.i18nc(
                "@label",
                "Are you sure you wish to print with the selected configuration?"
            )
            informative_text = i18n_catalog.i18nc(
                "@label",
                "There is a mismatch between the configuration or calibration of the printer and Cura. "
                "For the best result, always slice for the PrintCores and materials that are inserted in your printer."
            )
            detailed_text = ""
            for warning in warnings:
                detailed_text += warning + "\n"

            CuraApplication.getInstance().messageBox(
                i18n_catalog.i18nc("@window:title",
                                   "Mismatched configuration"),
                text,
                informative_text,
                detailed_text,
                buttons=QMessageBox.Yes + QMessageBox.No,
                icon=QMessageBox.Question,
                callback=self._messageBoxCallback)
            return

        # No warnings or errors, so we're good to go.
        self._startPrint()

        # Notify the UI that a switch to the print monitor should happen
        CuraApplication.getInstance().getController().setActiveStage(
            "MonitorStage")

    def _startPrint(self):
        Logger.log("i", "Sending print job to printer.")
        if self._sending_gcode:
            self._error_message = Message(
                i18n_catalog.i18nc(
                    "@info:status",
                    "Sending new jobs (temporarily) blocked, still sending the previous print job."
                ))
            self._error_message.show()
            return

        self._sending_gcode = True

        self._send_gcode_start = time()
        self._progress_message = Message(
            i18n_catalog.i18nc("@info:status", "Sending data to printer"), 0,
            False, -1, i18n_catalog.i18nc("@info:title", "Sending Data"))
        self._progress_message.addAction(
            "Abort", i18n_catalog.i18nc("@action:button", "Cancel"), None, "")
        self._progress_message.actionTriggered.connect(
            self._progressMessageActionTriggered)
        self._progress_message.show()

        compressed_gcode = self._compressGCode()
        if compressed_gcode is None:
            # Abort was called.
            return

        file_name = "%s.gcode.gz" % CuraApplication.getInstance(
        ).getPrintInformation().jobName
        self.postForm("print_job",
                      "form-data; name=\"file\";filename=\"%s\"" % file_name,
                      compressed_gcode,
                      onFinished=self._onPostPrintJobFinished)

        return

    def _progressMessageActionTriggered(self, message_id=None, action_id=None):
        if action_id == "Abort":
            Logger.log("d", "User aborted sending print to remote.")
            self._progress_message.hide()
            self._compressing_gcode = False
            self._sending_gcode = False
            CuraApplication.getInstance().getController().setActiveStage(
                "PrepareStage")

    def _onPostPrintJobFinished(self, reply):
        self._progress_message.hide()
        self._sending_gcode = False

    def _onUploadPrintJobProgress(self, bytes_sent, bytes_total):
        if bytes_total > 0:
            new_progress = bytes_sent / bytes_total * 100
            # Treat upload progress as response. Uploading can take more than 10 seconds, so if we don't, we can get
            # timeout responses if this happens.
            self._last_response_time = time()
            if new_progress > self._progress_message.getProgress():
                self._progress_message.show(
                )  # Ensure that the message is visible.
                self._progress_message.setProgress(bytes_sent / bytes_total *
                                                   100)
        else:
            self._progress_message.setProgress(0)

            self._progress_message.hide()

    def _messageBoxCallback(self, button):
        def delayedCallback():
            if button == QMessageBox.Yes:
                self._startPrint()
            else:
                CuraApplication.getInstance().getController().setActiveStage(
                    "PrepareStage")
                # For some unknown reason Cura on OSX will hang if we do the call back code
                # immediately without first returning and leaving QML's event system.

        QTimer.singleShot(100, delayedCallback)

    def _checkForErrors(self):
        errors = []
        print_information = CuraApplication.getInstance().getPrintInformation()
        if not print_information.materialLengths:
            Logger.log(
                "w",
                "There is no material length information. Unable to check for errors."
            )
            return errors

        for index, extruder in enumerate(self.activePrinter.extruders):
            # Due to airflow issues, both slots must be loaded, regardless if they are actually used or not.
            if extruder.hotendID == "":
                # No Printcore loaded.
                errors.append(
                    i18n_catalog.i18nc(
                        "@info:status",
                        "No Printcore loaded in slot {slot_number}".format(
                            slot_number=index + 1)))

            if index < len(print_information.materialLengths
                           ) and print_information.materialLengths[index] != 0:
                # The extruder is by this print.
                if extruder.activeMaterial is None:
                    # No active material
                    errors.append(
                        i18n_catalog.i18nc(
                            "@info:status",
                            "No material loaded in slot {slot_number}".format(
                                slot_number=index + 1)))
        return errors

    def _checkForWarnings(self):
        warnings = []
        print_information = CuraApplication.getInstance().getPrintInformation()

        if not print_information.materialLengths:
            Logger.log(
                "w",
                "There is no material length information. Unable to check for warnings."
            )
            return warnings

        extruder_manager = ExtruderManager.getInstance()

        for index, extruder in enumerate(self.activePrinter.extruders):
            if index < len(print_information.materialLengths
                           ) and print_information.materialLengths[index] != 0:
                # The extruder is by this print.

                # TODO: material length check

                # Check if the right Printcore is active.
                variant = extruder_manager.getExtruderStack(
                    index).findContainer({"type": "variant"})
                if variant:
                    if variant.getName() != extruder.hotendID:
                        warnings.append(
                            i18n_catalog.i18nc(
                                "@label",
                                "Different PrintCore (Cura: {cura_printcore_name}, Printer: {remote_printcore_name}) selected for extruder {extruder_id}"
                                .format(
                                    cura_printcore_name=variant.getName(),
                                    remote_printcore_name=extruder.hotendID,
                                    extruder_id=index + 1)))
                else:
                    Logger.log("w", "Unable to find variant.")

                # Check if the right material is loaded.
                local_material = extruder_manager.getExtruderStack(
                    index).findContainer({"type": "material"})
                if local_material:
                    if extruder.activeMaterial.guid != local_material.getMetaDataEntry(
                            "GUID"):
                        Logger.log(
                            "w",
                            "Extruder %s has a different material (%s) as Cura (%s)",
                            index + 1, extruder.activeMaterial.guid,
                            local_material.getMetaDataEntry("GUID"))
                        warnings.append(
                            i18n_catalog.i18nc(
                                "@label",
                                "Different material (Cura: {0}, Printer: {1}) selected for extruder {2}"
                            ).format(local_material.getName(),
                                     extruder.activeMaterial.name, index + 1))
                else:
                    Logger.log("w", "Unable to find material.")

        return warnings

    def _update(self):
        if not super()._update():
            return
        if self._authentication_state == AuthState.NotAuthenticated:
            if self._authentication_id is None and self._authentication_key is None:
                # This machine doesn't have any authentication, so request it.
                self._requestAuthentication()
            elif self._authentication_id is not None and self._authentication_key is not None:
                # We have authentication info, but we haven't checked it out yet. Do so now.
                self._verifyAuthentication()
        elif self._authentication_state == AuthState.AuthenticationReceived:
            # We have an authentication, but it's not confirmed yet.
            self._checkAuthentication()

        # We don't need authentication for requesting info, so we can go right ahead with requesting this.
        self.get("printer", onFinished=self._onGetPrinterDataFinished)
        self.get("print_job", onFinished=self._onGetPrintJobFinished)

    def _resetAuthenticationRequestedMessage(self):
        if self._authentication_requested_message:
            self._authentication_requested_message.hide()
        self._authentication_timer.stop()
        self._authentication_counter = 0

    def _onAuthenticationTimer(self):
        self._authentication_counter += 1
        self._authentication_requested_message.setProgress(
            self._authentication_counter / self._max_authentication_counter *
            100)
        if self._authentication_counter > self._max_authentication_counter:
            self._authentication_timer.stop()
            Logger.log(
                "i",
                "Authentication timer ended. Setting authentication to denied for printer: %s"
                % self._id)
            self.setAuthenticationState(AuthState.AuthenticationDenied)
            self._resetAuthenticationRequestedMessage()
            self._authentication_failed_message.show()

    def _verifyAuthentication(self):
        Logger.log("d", "Attempting to verify authentication")
        # This will ensure that the "_onAuthenticationRequired" is triggered, which will setup the authenticator.
        self.get("auth/verify",
                 onFinished=self._onVerifyAuthenticationCompleted)

    def _onVerifyAuthenticationCompleted(self, reply):
        status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
        if status_code == 401:
            # Something went wrong; We somehow tried to verify authentication without having one.
            Logger.log("d", "Attempted to verify auth without having one.")
            self._authentication_id = None
            self._authentication_key = None
            self.setAuthenticationState(AuthState.NotAuthenticated)
        elif status_code == 403 and self._authentication_state != AuthState.Authenticated:
            # If we were already authenticated, we probably got an older message back all of the sudden. Drop that.
            Logger.log(
                "d",
                "While trying to verify the authentication state, we got a forbidden response. Our own auth state was %s. ",
                self._authentication_state)
            self.setAuthenticationState(AuthState.AuthenticationDenied)
            self._authentication_failed_message.show()
        elif status_code == 200:
            self.setAuthenticationState(AuthState.Authenticated)

    def _checkAuthentication(self):
        Logger.log(
            "d", "Checking if authentication is correct for id %s and key %s",
            self._authentication_id, self._getSafeAuthKey())
        self.get("auth/check/" + str(self._authentication_id),
                 onFinished=self._onCheckAuthenticationFinished)

    def _onCheckAuthenticationFinished(self, reply):
        if str(self._authentication_id) not in reply.url().toString():
            Logger.log("w", "Got an old id response.")
            # Got response for old authentication ID.
            return
        try:
            data = json.loads(bytes(reply.readAll()).decode("utf-8"))
        except json.decoder.JSONDecodeError:
            Logger.log(
                "w",
                "Received an invalid authentication check from printer: Not valid JSON."
            )
            return

        if data.get("message", "") == "authorized":
            Logger.log("i", "Authentication was approved")
            self.setAuthenticationState(AuthState.Authenticated)
            self._saveAuthentication()

            # Double check that everything went well.
            self._verifyAuthentication()

            # Notify the user.
            self._resetAuthenticationRequestedMessage()
            self._authentication_succeeded_message.show()
        elif data.get("message", "") == "unauthorized":
            Logger.log("i", "Authentication was denied.")
            self.setAuthenticationState(AuthState.AuthenticationDenied)
            self._authentication_failed_message.show()

    def _saveAuthentication(self):
        global_container_stack = CuraApplication.getInstance(
        ).getGlobalContainerStack()
        if global_container_stack:
            if "network_authentication_key" in global_container_stack.getMetaData(
            ):
                global_container_stack.setMetaDataEntry(
                    "network_authentication_key", self._authentication_key)
            else:
                global_container_stack.addMetaDataEntry(
                    "network_authentication_key", self._authentication_key)

            if "network_authentication_id" in global_container_stack.getMetaData(
            ):
                global_container_stack.setMetaDataEntry(
                    "network_authentication_id", self._authentication_id)
            else:
                global_container_stack.addMetaDataEntry(
                    "network_authentication_id", self._authentication_id)

            # Force save so we are sure the data is not lost.
            CuraApplication.getInstance().saveStack(global_container_stack)
            Logger.log("i", "Authentication succeeded for id %s and key %s",
                       self._authentication_id, self._getSafeAuthKey())
        else:
            Logger.log("e",
                       "Unable to save authentication for id %s and key %s",
                       self._authentication_id, self._getSafeAuthKey())

    def _onRequestAuthenticationFinished(self, reply):
        try:
            data = json.loads(bytes(reply.readAll()).decode("utf-8"))
        except json.decoder.JSONDecodeError:
            Logger.log(
                "w",
                "Received an invalid authentication request reply from printer: Not valid JSON."
            )
            self.setAuthenticationState(AuthState.NotAuthenticated)
            return

        self.setAuthenticationState(AuthState.AuthenticationReceived)
        self._authentication_id = data["id"]
        self._authentication_key = data["key"]
        Logger.log(
            "i",
            "Got a new authentication ID (%s) and KEY (%s). Waiting for authorization.",
            self._authentication_id, self._getSafeAuthKey())

    def _requestAuthentication(self):
        self._authentication_requested_message.show()
        self._authentication_timer.start()

        # Reset any previous authentication info. If this isn't done, the "Retry" action on the failed message might
        # give issues.
        self._authentication_key = None
        self._authentication_id = None

        self.post("auth/request",
                  json.dumps({
                      "application":
                      "Cura-" + CuraApplication.getInstance().getVersion(),
                      "user":
                      self._getUserName()
                  }).encode(),
                  onFinished=self._onRequestAuthenticationFinished)

        self.setAuthenticationState(AuthState.AuthenticationRequested)

    def _onAuthenticationRequired(self, reply, authenticator):
        if self._authentication_id is not None and self._authentication_key is not None:
            Logger.log(
                "d",
                "Authentication was required for printer: %s. Setting up authenticator with ID %s and key %s",
                self._id, self._authentication_id, self._getSafeAuthKey())
            authenticator.setUser(self._authentication_id)
            authenticator.setPassword(self._authentication_key)
        else:
            Logger.log(
                "d",
                "No authentication is available to use for %s, but we did got a request for it.",
                self._id)

    def _onGetPrintJobFinished(self, reply):
        status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)

        if not self._printers:
            return  # Ignore the data for now, we don't have info about a printer yet.
        printer = self._printers[0]

        if status_code == 200:
            try:
                result = json.loads(bytes(reply.readAll()).decode("utf-8"))
            except json.decoder.JSONDecodeError:
                Logger.log(
                    "w",
                    "Received an invalid print job state message: Not valid JSON."
                )
                return
            if printer.activePrintJob is None:
                print_job = PrintJobOutputModel(
                    output_controller=self._output_controller)
                printer.updateActivePrintJob(print_job)
            else:
                print_job = printer.activePrintJob
            print_job.updateState(result["state"])
            print_job.updateTimeElapsed(result["time_elapsed"])
            print_job.updateTimeTotal(result["time_total"])
            print_job.updateName(result["name"])
        elif status_code == 404:
            # No job found, so delete the active print job (if any!)
            printer.updateActivePrintJob(None)
        else:
            Logger.log(
                "w",
                "Got status code {status_code} while trying to get printer data"
                .format(status_code=status_code))

    def materialHotendChangedMessage(self, callback):
        CuraApplication.getInstance().messageBox(
            i18n_catalog.i18nc("@window:title", "Sync with your printer"),
            i18n_catalog.i18nc(
                "@label",
                "Would you like to use your current printer configuration in Cura?"
            ),
            i18n_catalog.i18nc(
                "@label",
                "The PrintCores and/or materials on your printer differ from those within your current project. For the best result, always slice for the PrintCores and materials that are inserted in your printer."
            ),
            buttons=QMessageBox.Yes + QMessageBox.No,
            icon=QMessageBox.Question,
            callback=callback)

    def _onGetPrinterDataFinished(self, reply):
        status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
        if status_code == 200:
            try:
                result = json.loads(bytes(reply.readAll()).decode("utf-8"))
            except json.decoder.JSONDecodeError:
                Logger.log(
                    "w",
                    "Received an invalid printer state message: Not valid JSON."
                )
                return

            if not self._printers:
                # Quickest way to get the firmware version is to grab it from the zeroconf.
                firmware_version = self._properties.get(
                    b"firmware_version", b"").decode("utf-8")
                self._printers = [
                    PrinterOutputModel(
                        output_controller=self._output_controller,
                        number_of_extruders=self._number_of_extruders,
                        firmware_version=firmware_version)
                ]
                self._printers[0].setCamera(
                    NetworkCamera("http://" + self._address +
                                  ":8080/?action=stream"))
                for extruder in self._printers[0].extruders:
                    extruder.activeMaterialChanged.connect(
                        self.materialIdChanged)
                    extruder.hotendIDChanged.connect(self.hotendIdChanged)
                self.printersChanged.emit()

            # LegacyUM3 always has a single printer.
            printer = self._printers[0]
            printer.updateBedTemperature(
                result["bed"]["temperature"]["current"])
            printer.updateTargetBedTemperature(
                result["bed"]["temperature"]["target"])
            printer.updateState(result["status"])

            try:
                # If we're still handling the request, we should ignore remote for a bit.
                if not printer.getController().isPreheatRequestInProgress():
                    printer.updateIsPreheating(
                        result["bed"]["pre_heat"]["active"])
            except KeyError:
                # Older firmwares don't support preheating, so we need to fake it.
                pass

            head_position = result["heads"][0]["position"]
            printer.updateHeadPosition(head_position["x"], head_position["y"],
                                       head_position["z"])

            for index in range(0, self._number_of_extruders):
                temperatures = result["heads"][0]["extruders"][index][
                    "hotend"]["temperature"]
                extruder = printer.extruders[index]
                extruder.updateTargetHotendTemperature(temperatures["target"])
                extruder.updateHotendTemperature(temperatures["current"])

                material_guid = result["heads"][0]["extruders"][index][
                    "active_material"]["guid"]

                if extruder.activeMaterial is None or extruder.activeMaterial.guid != material_guid:
                    # Find matching material (as we need to set brand, type & color)
                    containers = ContainerRegistry.getInstance(
                    ).findInstanceContainers(type="material",
                                             GUID=material_guid)
                    if containers:
                        color = containers[0].getMetaDataEntry("color_code")
                        brand = containers[0].getMetaDataEntry("brand")
                        material_type = containers[0].getMetaDataEntry(
                            "material")
                        name = containers[0].getName()
                    else:
                        # Unknown material.
                        color = "#00000000"
                        brand = "Unknown"
                        material_type = "Unknown"
                        name = "Unknown"
                    material = MaterialOutputModel(guid=material_guid,
                                                   type=material_type,
                                                   brand=brand,
                                                   color=color,
                                                   name=name)
                    extruder.updateActiveMaterial(material)

                try:
                    hotend_id = result["heads"][0]["extruders"][index][
                        "hotend"]["id"]
                except KeyError:
                    hotend_id = ""
                printer.extruders[index].updateHotendID(hotend_id)

        else:
            Logger.log(
                "w",
                "Got status code {status_code} while trying to get printer data"
                .format(status_code=status_code))

    ##  Convenience function to "blur" out all but the last 5 characters of the auth key.
    #   This can be used to debug print the key, without it compromising the security.
    def _getSafeAuthKey(self):
        if self._authentication_key is not None:
            result = self._authentication_key[-5:]
            result = "********" + result
            return result

        return self._authentication_key
class OctoPrintOutputDevice(NetworkedPrinterOutputDevice):
    def __init__(self, key, address: str, port, properties, parent=None):
        super().__init__(device_id=key,
                         address=address,
                         properties=properties,
                         parent=parent)

        self._address = address
        self._port = port
        self._path = properties.get(b"path", b"/").decode("utf-8")
        if self._path[-1:] != "/":
            self._path += "/"
        self._key = key
        self._properties = properties  # Properties dict as provided by zero conf

        self._gcode = None
        self._auto_print = True
        self._forced_queue = False

        # We start with a single extruder, but update this when we get data from octoprint
        self._number_of_extruders_set = False
        self._number_of_extruders = 1

        # Try to get version information from plugin.json
        plugin_file_path = os.path.join(
            os.path.dirname(os.path.abspath(__file__)), "plugin.json")
        try:
            with open(plugin_file_path) as plugin_file:
                plugin_info = json.load(plugin_file)
                plugin_version = plugin_info["version"]
        except:
            # The actual version info is not critical to have so we can continue
            plugin_version = "Unknown"
            Logger.logException(
                "w", "Could not get version information for the plugin")

        self._user_agent_header = "User-Agent".encode()
        self._user_agent = (
            "%s/%s %s/%s" %
            (Application.getInstance().getApplicationName(),
             Application.getInstance().getVersion(), "OctoPrintPlugin",
             Application.getInstance().getVersion())).encode()

        self._api_prefix = "api/"
        self._api_header = "X-Api-Key".encode()
        self._api_key = None

        self._protocol = "https" if properties.get(
            b'useHttps') == b"true" else "http"
        self._base_url = "%s://%s:%d%s" % (self._protocol, self._address,
                                           self._port, self._path)
        self._api_url = self._base_url + self._api_prefix

        self._basic_auth_header = "Authorization".encode()
        self._basic_auth_data = None
        basic_auth_username = properties.get(b"userName", b"").decode("utf-8")
        basic_auth_password = properties.get(b"password", b"").decode("utf-8")
        if basic_auth_username and basic_auth_password:
            data = base64.b64encode(
                ("%s:%s" % (basic_auth_username,
                            basic_auth_password)).encode()).decode("utf-8")
            self._basic_auth_data = ("basic %s" % data).encode()

        self._monitor_view_qml_path = os.path.join(
            os.path.dirname(os.path.abspath(__file__)), "MonitorItem.qml")

        self.setPriority(
            2
        )  # Make sure the output device gets selected above local file output
        self.setName(key)
        self.setShortDescription(
            i18n_catalog.i18nc("@action:button", "Print with OctoPrint"))
        self.setDescription(
            i18n_catalog.i18nc("@properties:tooltip", "Print with OctoPrint"))
        self.setIconName("print")
        self.setConnectionText(
            i18n_catalog.i18nc("@info:status",
                               "Connected to OctoPrint on {0}").format(
                                   self._key))

        #   QNetwork manager needs to be created in advance. If we don't it can happen that it doesn't correctly
        #   hook itself into the event loop, which results in events never being fired / done.
        self._manager = QNetworkAccessManager()
        self._manager.finished.connect(self._onRequestFinished)

        ##  Ensure that the qt networking stuff isn't garbage collected (unless we want it to)
        self._settings_reply = None
        self._printer_reply = None
        self._job_reply = None
        self._command_reply = None

        self._post_reply = None
        self._post_multi_part = None
        self._post_part = None

        self._progress_message = None
        self._error_message = None
        self._connection_message = None

        self._queued_gcode_commands = []
        self._queued_gcode_timer = QTimer()
        self._queued_gcode_timer.setInterval(0)
        self._queued_gcode_timer.setSingleShot(True)
        self._queued_gcode_timer.timeout.connect(self._sendQueuedGcode)

        self._update_timer = QTimer()
        self._update_timer.setInterval(
            2000)  # TODO; Add preference for update interval
        self._update_timer.setSingleShot(False)
        self._update_timer.timeout.connect(self._update)

        self._camera_mirror = ""
        self._camera_rotation = 0
        self._camera_url = ""
        self._camera_shares_proxy = False

        self._sd_supported = False

        self._connection_state_before_timeout = None

        self._last_response_time = None
        self._last_request_time = None
        self._response_timeout_time = 5
        self._recreate_network_manager_time = 30  # If we have no connection, re-create network manager every 30 sec.
        self._recreate_network_manager_count = 1

        self._output_controller = GenericOutputController(self)

    def getProperties(self):
        return self._properties

    @pyqtSlot(str, result=str)
    def getProperty(self, key):
        key = key.encode("utf-8")
        if key in self._properties:
            return self._properties.get(key, b"").decode("utf-8")
        else:
            return ""

    ##  Get the unique key of this machine
    #   \return key String containing the key of the machine.
    @pyqtSlot(result=str)
    def getKey(self):
        return self._key

    ##  Set the API key of this OctoPrint instance
    def setApiKey(self, api_key):
        self._api_key = api_key.encode()

    ##  Name of the instance (as returned from the zeroConf properties)
    @pyqtProperty(str, constant=True)
    def name(self):
        return self._key

    ##  Version (as returned from the zeroConf properties)
    @pyqtProperty(str, constant=True)
    def octoprintVersion(self):
        return self._properties.get(b"version", b"").decode("utf-8")

    ## IPadress of this instance
    @pyqtProperty(str, constant=True)
    def ipAddress(self):
        return self._address

    ## IPadress of this instance
    #  Overridden from NetworkedPrinterOutputDevice because OctoPrint does not
    #  send the ip address with zeroconf
    @pyqtProperty(str, constant=True)
    def address(self):
        return self._address

    ## port of this instance
    @pyqtProperty(int, constant=True)
    def port(self):
        return self._port

    ## path of this instance
    @pyqtProperty(str, constant=True)
    def path(self):
        return self._path

    ## absolute url of this instance
    @pyqtProperty(str, constant=True)
    def baseURL(self):
        return self._base_url

    cameraOrientationChanged = pyqtSignal()

    @pyqtProperty("QVariantMap", notify=cameraOrientationChanged)
    def cameraOrientation(self):
        return {
            "mirror": self._camera_mirror,
            "rotation": self._camera_rotation,
        }

    def _update(self):
        if self._last_response_time:
            time_since_last_response = time() - self._last_response_time
        else:
            time_since_last_response = 0
        if self._last_request_time:
            time_since_last_request = time() - self._last_request_time
        else:
            time_since_last_request = float(
                "inf")  # An irrelevantly large number of seconds

        # Connection is in timeout, check if we need to re-start the connection.
        # Sometimes the qNetwork manager incorrectly reports the network status on Mac & Windows.
        # Re-creating the QNetworkManager seems to fix this issue.
        if self._last_response_time and self._connection_state_before_timeout:
            if time_since_last_response > self._recreate_network_manager_time * self._recreate_network_manager_count:
                self._recreate_network_manager_count += 1
                # It can happen that we had a very long timeout (multiple times the recreate time).
                # In that case we should jump through the point that the next update won't be right away.
                while time_since_last_response - self._recreate_network_manager_time * self._recreate_network_manager_count > self._recreate_network_manager_time:
                    self._recreate_network_manager_count += 1
                Logger.log(
                    "d",
                    "Timeout lasted over 30 seconds (%.1fs), re-checking connection.",
                    time_since_last_response)
                self._createNetworkManager()
                return

        # Check if we have an connection in the first place.
        if not self._manager.networkAccessible():
            if not self._connection_state_before_timeout:
                Logger.log(
                    "d",
                    "The network connection seems to be disabled. Going into timeout mode"
                )
                self._connection_state_before_timeout = self._connection_state
                self.setConnectionState(ConnectionState.error)
                self._connection_message = Message(
                    i18n_catalog.i18nc(
                        "@info:status",
                        "The connection with the network was lost."))
                self._connection_message.show()
                # Check if we were uploading something. Abort if this is the case.
                # Some operating systems handle this themselves, others give weird issues.
                try:
                    if self._post_reply:
                        Logger.log(
                            "d",
                            "Stopping post upload because the connection was lost."
                        )
                        try:
                            self._post_reply.uploadProgress.disconnect(
                                self._onUploadProgress)
                        except TypeError:
                            pass  # The disconnection can fail on mac in some cases. Ignore that.

                        self._post_reply.abort()
                        self._progress_message.hide()
                except RuntimeError:
                    self._post_reply = None  # It can happen that the wrapped c++ object is already deleted.
            return
        else:
            if not self._connection_state_before_timeout:
                self._recreate_network_manager_count = 1

        # Check that we aren't in a timeout state
        if self._last_response_time and self._last_request_time and not self._connection_state_before_timeout:
            if time_since_last_response > self._response_timeout_time and time_since_last_request <= self._response_timeout_time:
                # Go into timeout state.
                Logger.log(
                    "d",
                    "We did not receive a response for %s seconds, so it seems OctoPrint is no longer accesible.",
                    time() - self._last_response_time)
                self._connection_state_before_timeout = self._connection_state
                self._connection_message = Message(
                    i18n_catalog.i18nc(
                        "@info:status",
                        "The connection with OctoPrint was lost. Check your network-connections."
                    ))
                self._connection_message.show()
                self.setConnectionState(ConnectionState.error)

        ## Request 'general' printer data
        self._printer_reply = self._manager.get(
            self._createApiRequest("printer"))

        ## Request print_job data
        self._job_reply = self._manager.get(self._createApiRequest("job"))

    def _createNetworkManager(self):
        if self._manager:
            self._manager.finished.disconnect(self._onRequestFinished)

        self._manager = QNetworkAccessManager()
        self._manager.finished.connect(self._onRequestFinished)

    def _createApiRequest(self, end_point):
        request = QNetworkRequest(QUrl(self._api_url + end_point))
        request.setRawHeader(self._user_agent_header, self._user_agent)
        request.setRawHeader(self._api_header, self._api_key)
        if self._basic_auth_data:
            request.setRawHeader(self._basic_auth_header,
                                 self._basic_auth_data)
        return request

    def close(self):
        self.setConnectionState(ConnectionState.closed)
        if self._progress_message:
            self._progress_message.hide()
        if self._error_message:
            self._error_message.hide()
        self._update_timer.stop()

    def requestWrite(self,
                     node,
                     file_name=None,
                     filter_by_machine=False,
                     file_handler=None,
                     **kwargs):
        self.writeStarted.emit(self)

        active_build_plate = Application.getInstance().getBuildPlateModel(
        ).activeBuildPlate
        scene = Application.getInstance().getController().getScene()
        gcode_dict = getattr(scene, "gcode_dict", None)
        if not gcode_dict:
            return
        self._gcode = gcode_dict.get(active_build_plate, None)

        self.startPrint()

    ##  Start requesting data from the instance
    def connect(self):
        self._createNetworkManager()

        self.setConnectionState(ConnectionState.connecting)
        self._update(
        )  # Manually trigger the first update, as we don't want to wait a few secs before it starts.
        Logger.log("d", "Connection with instance %s with url %s started",
                   self._key, self._base_url)
        self._update_timer.start()

        self._last_response_time = None
        self._setAcceptsCommands(False)
        self.setConnectionText(
            i18n_catalog.i18nc("@info:status",
                               "Connecting to OctoPrint on {0}").format(
                                   self._key))

        ## Request 'settings' dump
        self._settings_reply = self._manager.get(
            self._createApiRequest("settings"))

    ##  Stop requesting data from the instance
    def disconnect(self):
        Logger.log("d", "Connection with instance %s with url %s stopped",
                   self._key, self._base_url)
        self.close()

    def pausePrint(self):
        self._sendJobCommand("pause")

    def resumePrint(self):
        if not self._printers[0].activePrintJob:
            return

        if self._printers[0].activePrintJob.state == "paused":
            self._sendJobCommand("pause")
        else:
            self._sendJobCommand("start")

    def cancelPrint(self):
        self._sendJobCommand("cancel")

    def startPrint(self):
        global_container_stack = Application.getInstance(
        ).getGlobalContainerStack()
        if not global_container_stack:
            return

        if self._error_message:
            self._error_message.hide()
            self._error_message = None

        if self._progress_message:
            self._progress_message.hide()
            self._progress_message = None

        self._auto_print = parseBool(
            global_container_stack.getMetaDataEntry("octoprint_auto_print",
                                                    True))
        self._forced_queue = False

        if self.activePrinter.state not in ["idle", ""]:
            Logger.log(
                "d", "Tried starting a print, but current state is %s" %
                self.activePrinter.state)
            if self.activePrinter.state == "offline":
                self._error_message = Message(
                    i18n_catalog.i18nc(
                        "@info:status",
                        "The printer is offline. Unable to start a new job."))
            elif self._auto_print:
                self._error_message = Message(
                    i18n_catalog.i18nc(
                        "@info:status",
                        "OctoPrint is busy. Unable to start a new job."))
            else:
                # allow queueing the job even if OctoPrint is currently busy if autoprinting is disabled
                self._error_message = None

            if self._error_message:
                self._error_message.addAction(
                    "Queue", i18n_catalog.i18nc("@action:button", "Queue job"),
                    None,
                    i18n_catalog.i18nc(
                        "@action:tooltip",
                        "Queue this print job so it can be printed later"))
                self._error_message.actionTriggered.connect(self._queuePrint)
                self._error_message.show()
                return

        self._startPrint()

    def _queuePrint(self, message_id, action_id):
        if self._error_message:
            self._error_message.hide()
        self._forced_queue = True
        self._startPrint()

    def _startPrint(self):
        if self._auto_print and not self._forced_queue:
            Application.getInstance().getController().setActiveStage(
                "MonitorStage")

        try:
            self._progress_message = Message(
                i18n_catalog.i18nc("@info:status",
                                   "Sending data to OctoPrint"), 0, False, -1)
            self._progress_message.addAction(
                "Cancel", i18n_catalog.i18nc("@action:button", "Cancel"), None,
                "")
            self._progress_message.actionTriggered.connect(
                self._cancelSendGcode)
            self._progress_message.show()

            ## Mash the data into single string
            single_string_file_data = ""
            last_process_events = time()
            for line in self._gcode:
                single_string_file_data += line
                if time() > last_process_events + 0.05:
                    # Ensure that the GUI keeps updated at least 20 times per second.
                    QCoreApplication.processEvents()
                    last_process_events = time()

            job_name = Application.getInstance().getPrintInformation(
            ).jobName.strip()
            if job_name is "":
                job_name = "untitled_print"
            file_name = "%s.gcode" % job_name

            ##  Create multi_part request
            self._post_multi_part = QHttpMultiPart(QHttpMultiPart.FormDataType)

            ##  Create parts (to be placed inside multipart)
            self._post_part = QHttpPart()
            self._post_part.setHeader(QNetworkRequest.ContentDispositionHeader,
                                      "form-data; name=\"select\"")
            self._post_part.setBody(b"true")
            self._post_multi_part.append(self._post_part)

            if self._auto_print and not self._forced_queue:
                self._post_part = QHttpPart()
                self._post_part.setHeader(
                    QNetworkRequest.ContentDispositionHeader,
                    "form-data; name=\"print\"")
                self._post_part.setBody(b"true")
                self._post_multi_part.append(self._post_part)

            self._post_part = QHttpPart()
            self._post_part.setHeader(
                QNetworkRequest.ContentDispositionHeader,
                "form-data; name=\"file\"; filename=\"%s\"" % file_name)
            self._post_part.setBody(single_string_file_data.encode())
            self._post_multi_part.append(self._post_part)

            destination = "local"
            if self._sd_supported and parseBool(Application.getInstance(
            ).getGlobalContainerStack().getMetaDataEntry(
                    "octoprint_store_sd", False)):
                destination = "sdcard"

            ##  Post request + data
            post_request = self._createApiRequest("files/" + destination)
            self._post_reply = self._manager.post(post_request,
                                                  self._post_multi_part)
            self._post_reply.uploadProgress.connect(self._onUploadProgress)

            self._gcode = None

        except IOError:
            self._progress_message.hide()
            self._error_message = Message(
                i18n_catalog.i18nc("@info:status",
                                   "Unable to send data to OctoPrint."))
            self._error_message.show()
        except Exception as e:
            self._progress_message.hide()
            Logger.log(
                "e",
                "An exception occurred in network connection: %s" % str(e))

    def _cancelSendGcode(self, message_id, action_id):
        if self._post_reply:
            Logger.log("d", "Stopping upload because the user pressed cancel.")
            try:
                self._post_reply.uploadProgress.disconnect(
                    self._onUploadProgress)
            except TypeError:
                pass  # The disconnection can fail on mac in some cases. Ignore that.

            self._post_reply.abort()
            self._post_reply = None
        if self._progress_message:
            self._progress_message.hide()

    def sendCommand(self, command):
        self._queued_gcode_commands.append(command)
        self._queued_gcode_timer.start()

    # Send gcode commands that are queued in quick succession as a single batch
    def _sendQueuedGcode(self):
        if self._queued_gcode_commands:
            self._sendCommandToApi("printer/command",
                                   self._queued_gcode_commands)
            Logger.log("d", "Sent gcode command to OctoPrint instance: %s",
                       self._queued_gcode_commands)
            self._queued_gcode_commands = []

    def _sendJobCommand(self, command):
        self._sendCommandToApi("job", command)
        Logger.log("d", "Sent job command to OctoPrint instance: %s", command)

    def _sendCommandToApi(self, end_point, commands):
        command_request = self._createApiRequest(end_point)
        command_request.setHeader(QNetworkRequest.ContentTypeHeader,
                                  "application/json")

        if isinstance(commands, list):
            data = json.dumps({"commands": commands})
        else:
            data = json.dumps({"command": commands})
        self._command_reply = self._manager.post(command_request,
                                                 data.encode())

    ##  Handler for all requests that have finished.
    def _onRequestFinished(self, reply):
        if reply.error() == QNetworkReply.TimeoutError:
            Logger.log("w", "Received a timeout on a request to the instance")
            self._connection_state_before_timeout = self._connection_state
            self.setConnectionState(ConnectionState.error)
            return

        if self._connection_state_before_timeout and reply.error(
        ) == QNetworkReply.NoError:  #  There was a timeout, but we got a correct answer again.
            if self._last_response_time:
                Logger.log(
                    "d",
                    "We got a response from the instance after %s of silence",
                    time() - self._last_response_time)
            self.setConnectionState(self._connection_state_before_timeout)
            self._connection_state_before_timeout = None

        if reply.error() == QNetworkReply.NoError:
            self._last_response_time = time()

        http_status_code = reply.attribute(
            QNetworkRequest.HttpStatusCodeAttribute)
        if not http_status_code:
            # Received no or empty reply
            return

        if reply.operation() == QNetworkAccessManager.GetOperation:
            if self._api_prefix + "printer" in reply.url().toString(
            ):  # Status update from /printer.
                if not self._printers:
                    self._createPrinterList()

                # An OctoPrint instance has a single printer.
                printer = self._printers[0]

                if http_status_code == 200:
                    if not self.acceptsCommands:
                        self._setAcceptsCommands(True)
                        self.setConnectionText(
                            i18n_catalog.i18nc(
                                "@info:status",
                                "Connected to OctoPrint on {0}").format(
                                    self._key))

                    if self._connection_state == ConnectionState.connecting:
                        self.setConnectionState(ConnectionState.connected)
                    try:
                        json_data = json.loads(
                            bytes(reply.readAll()).decode("utf-8"))
                    except json.decoder.JSONDecodeError:
                        Logger.log(
                            "w",
                            "Received invalid JSON from octoprint instance.")
                        json_data = {}

                    if "temperature" in json_data:
                        if not self._number_of_extruders_set:
                            self._number_of_extruders = 0
                            while "tool%d" % self._number_of_extruders in json_data[
                                    "temperature"]:
                                self._number_of_extruders += 1

                            if self._number_of_extruders > 1:
                                # Recreate list of printers to match the new _number_of_extruders
                                self._createPrinterList()
                                printer = self._printers[0]

                            if self._number_of_extruders > 0:
                                self._number_of_extruders_set = True

                        # Check for hotend temperatures
                        for index in range(0, self._number_of_extruders):
                            extruder = printer.extruders[index]
                            if ("tool%d" % index) in json_data["temperature"]:
                                hotend_temperatures = json_data["temperature"][
                                    "tool%d" % index]
                                extruder.updateTargetHotendTemperature(
                                    hotend_temperatures["target"])
                                extruder.updateHotendTemperature(
                                    hotend_temperatures["actual"])
                            else:
                                extruder.updateTargetHotendTemperature(0)
                                extruder.updateHotendTemperature(0)

                        if "bed" in json_data["temperature"]:
                            bed_temperatures = json_data["temperature"]["bed"]
                            printer.updateBedTemperature(
                                bed_temperatures["actual"])
                            printer.updateTargetBedTemperature(
                                bed_temperatures["target"])
                        else:
                            printer.updateBedTemperature(0)
                            printer.updateTargetBedTemperature(0)

                    printer_state = "offline"
                    if "state" in json_data:
                        if json_data["state"]["flags"]["error"]:
                            printer_state = "error"
                        elif json_data["state"]["flags"]["paused"]:
                            printer_state = "paused"
                        elif json_data["state"]["flags"]["printing"]:
                            printer_state = "printing"
                        elif json_data["state"]["flags"]["ready"]:
                            printer_state = "idle"
                    printer.updateState(printer_state)

                elif http_status_code == 401:
                    printer.updateState("offline")
                    if printer.activePrintJob:
                        printer.activePrintJob.updateState("offline")
                    self.setConnectionText(
                        i18n_catalog.i18nc(
                            "@info:status",
                            "OctoPrint on {0} does not allow access to print").
                        format(self._key))
                    pass
                elif http_status_code == 409:
                    if self._connection_state == ConnectionState.connecting:
                        self.setConnectionState(ConnectionState.connected)

                    printer.updateState("offline")
                    if printer.activePrintJob:
                        printer.activePrintJob.updateState("offline")
                    self.setConnectionText(
                        i18n_catalog.i18nc(
                            "@info:status",
                            "The printer connected to OctoPrint on {0} is not operational"
                        ).format(self._key))
                else:
                    printer.updateState("offline")
                    if printer.activePrintJob:
                        printer.activePrintJob.updateState("offline")
                    Logger.log("w", "Received an unexpected returncode: %d",
                               http_status_code)

            elif self._api_prefix + "job" in reply.url().toString(
            ):  # Status update from /job:
                if not self._printers:
                    return  # Ignore the data for now, we don't have info about a printer yet.
                printer = self._printers[0]

                if http_status_code == 200:
                    try:
                        json_data = json.loads(
                            bytes(reply.readAll()).decode("utf-8"))
                    except json.decoder.JSONDecodeError:
                        Logger.log(
                            "w",
                            "Received invalid JSON from octoprint instance.")
                        json_data = {}

                    if printer.activePrintJob is None:
                        print_job = PrintJobOutputModel(
                            output_controller=self._output_controller)
                        printer.updateActivePrintJob(print_job)
                    else:
                        print_job = printer.activePrintJob

                    print_job_state = "offline"
                    if "state" in json_data:
                        if json_data["state"] == "Error":
                            print_job_state = "error"
                        elif json_data["state"] == "Paused":
                            print_job_state = "paused"
                        elif json_data["state"] == "Printing":
                            print_job_state = "printing"
                        elif json_data["state"] == "Operational":
                            print_job_state = "ready"
                            printer.updateState("idle")
                    print_job.updateState(print_job_state)

                    if json_data["progress"]["printTime"]:
                        print_job.updateTimeElapsed(
                            json_data["progress"]["printTime"])
                        if json_data["progress"]["printTimeLeft"]:
                            print_job.updateTimeTotal(
                                json_data["progress"]["printTime"] +
                                json_data["progress"]["printTimeLeft"])
                        elif json_data["job"]["estimatedPrintTime"]:
                            print_job.updateTimeTotal(
                                max(json_data["job"]["estimatedPrintTime"],
                                    json_data["progress"]["printTime"]))
                        elif json_data["progress"][
                                "completion"]:  # not 0 or None or ""
                            print_job.updateTimeTotal(
                                json_data["progress"]["printTime"] /
                                (json_data["progress"]["completion"] / 100))
                        else:
                            print_job.updateTimeTotal(0)
                    else:
                        print_job.updateTimeElapsed(0)
                        print_job.updateTimeTotal(0)

                    print_job.updateName(json_data["job"]["file"]["name"])
                else:
                    pass  # TODO: Handle errors

            elif self._api_prefix + "settings" in reply.url().toString(
            ):  # OctoPrint settings dump from /settings:
                if http_status_code == 200:
                    try:
                        json_data = json.loads(
                            bytes(reply.readAll()).decode("utf-8"))
                    except json.decoder.JSONDecodeError:
                        Logger.log(
                            "w",
                            "Received invalid JSON from octoprint instance.")
                        json_data = {}

                    if "feature" in json_data and "sdSupport" in json_data[
                            "feature"]:
                        self._sd_supported = json_data["feature"]["sdSupport"]

                    if "webcam" in json_data and "streamUrl" in json_data[
                            "webcam"]:
                        self._camera_shares_proxy = False
                        stream_url = json_data["webcam"]["streamUrl"]
                        if not stream_url:  #empty string or None
                            self._camera_url = ""
                        elif stream_url[:4].lower() == "http":  # absolute uri
                            self._camera_url = stream_url
                        elif stream_url[:2] == "//":  # protocol-relative
                            self._camera_url = "%s:%s" % (self._protocol,
                                                          stream_url)
                        elif stream_url[:
                                        1] == ":":  # domain-relative (on another port)
                            self._camera_url = "%s://%s%s" % (
                                self._protocol, self._address, stream_url)
                        elif stream_url[:
                                        1] == "/":  # domain-relative (on same port)
                            self._camera_url = "%s://%s:%d%s" % (
                                self._protocol, self._address, self._port,
                                stream_url)
                            self._camera_shares_proxy = True
                        else:
                            Logger.log("w", "Unusable stream url received: %s",
                                       stream_url)
                            self._camera_url = ""

                        Logger.log("d", "Set OctoPrint camera url to %s",
                                   self._camera_url)

                        if "rotate90" in json_data["webcam"]:
                            self._camera_rotation = -90 if json_data["webcam"][
                                "rotate90"] else 0
                            if json_data["webcam"]["flipH"] and json_data[
                                    "webcam"]["flipV"]:
                                self._camera_mirror = False
                                self._camera_rotation += 180
                            elif json_data["webcam"]["flipH"]:
                                self._camera_mirror = True
                            elif json_data["webcam"]["flipV"]:
                                self._camera_mirror = True
                                self._camera_rotation += 180
                            else:
                                self._camera_mirror = False
                            self.cameraOrientationChanged.emit()

        elif reply.operation() == QNetworkAccessManager.PostOperation:
            if self._api_prefix + "files" in reply.url().toString(
            ):  # Result from /files command:
                if http_status_code == 201:
                    Logger.log(
                        "d", "Resource created on OctoPrint instance: %s",
                        reply.header(
                            QNetworkRequest.LocationHeader).toString())
                else:
                    pass  # TODO: Handle errors

                reply.uploadProgress.disconnect(self._onUploadProgress)
                self._progress_message.hide()

                if self._forced_queue or not self._auto_print:
                    location = reply.header(QNetworkRequest.LocationHeader)
                    if location:
                        file_name = QUrl(
                            reply.header(QNetworkRequest.LocationHeader).
                            toString()).fileName()
                        message = Message(
                            i18n_catalog.i18nc(
                                "@info:status",
                                "Saved to OctoPrint as {0}").format(file_name))
                    else:
                        message = Message(
                            i18n_catalog.i18nc("@info:status",
                                               "Saved to OctoPrint"))
                    message.addAction(
                        "open_browser",
                        i18n_catalog.i18nc("@action:button", "OctoPrint..."),
                        "globe",
                        i18n_catalog.i18nc("@info:tooltip",
                                           "Open the OctoPrint web interface"))
                    message.actionTriggered.connect(
                        self._onMessageActionTriggered)
                    message.show()

            elif self._api_prefix + "job" in reply.url().toString(
            ):  # Result from /job command (eg start/pause):
                if http_status_code == 204:
                    Logger.log("d", "Octoprint job command accepted")
                else:
                    pass  # TODO: Handle errors

            elif self._api_prefix + "printer/command" in reply.url().toString(
            ):  # Result from /printer/command (gcode statements):
                if http_status_code == 204:
                    Logger.log("d", "Octoprint gcode command(s) accepted")
                else:
                    pass  # TODO: Handle errors

        else:
            Logger.log("d",
                       "OctoPrintOutputDevice got an unhandled operation %s",
                       reply.operation())

    def _onUploadProgress(self, bytes_sent, bytes_total):
        if bytes_total > 0:
            # Treat upload progress as response. Uploading can take more than 10 seconds, so if we don't, we can get
            # timeout responses if this happens.
            self._last_response_time = time()

            progress = bytes_sent / bytes_total * 100
            if progress < 100:
                if progress > self._progress_message.getProgress():
                    self._progress_message.setProgress(progress)
            else:
                self._progress_message.hide()
                self._progress_message = Message(
                    i18n_catalog.i18nc("@info:status",
                                       "Storing data on OctoPrint"), 0, False,
                    -1)
                self._progress_message.show()
        else:
            self._progress_message.setProgress(0)

    def _createPrinterList(self):
        printer = PrinterOutputModel(
            output_controller=self._output_controller,
            number_of_extruders=self._number_of_extruders)
        printer.setCamera(NetworkCamera(self._camera_url))
        printer.updateName(self.name)
        self._printers = [printer]
        self.printersChanged.emit()

    def _onMessageActionTriggered(self, message, action):
        if action == "open_browser":
            QDesktopServices.openUrl(QUrl(self._base_url))
Exemplo n.º 43
0
class LegacyUM3PrinterOutputController(PrinterOutputController):
    def __init__(self, output_device):
        super().__init__(output_device)
        self._preheat_bed_timer = QTimer()
        self._preheat_bed_timer.setSingleShot(True)
        self._preheat_bed_timer.timeout.connect(
            self._onPreheatBedTimerFinished)
        self._preheat_printer = None

        self.can_control_manually = False
        self.can_send_raw_gcode = False

        # Are we still waiting for a response about preheat?
        # We need this so we can already update buttons, so it feels more snappy.
        self._preheat_request_in_progress = False

    def isPreheatRequestInProgress(self):
        return self._preheat_request_in_progress

    def setJobState(self, job: "PrintJobOutputModel", state: str):
        data = "{\"target\": \"%s\"}" % state
        self._output_device.put("print_job/state", data, onFinished=None)

    def setTargetBedTemperature(self, printer: "PrinterOutputModel",
                                temperature: int):
        data = str(temperature)
        self._output_device.put("printer/bed/temperature/target",
                                data,
                                onFinished=self._onPutBedTemperatureCompleted)

    def _onPutBedTemperatureCompleted(self, reply):
        if Version(self._preheat_printer.firmwareVersion) < Version("3.5.92"):
            # If it was handling a preheat, it isn't anymore.
            self._preheat_request_in_progress = False

    def _onPutPreheatBedCompleted(self, reply):
        self._preheat_request_in_progress = False

    def moveHead(self, printer: "PrinterOutputModel", x, y, z, speed):
        head_pos = printer._head_position
        new_x = head_pos.x + x
        new_y = head_pos.y + y
        new_z = head_pos.z + z
        data = "{\n\"x\":%s,\n\"y\":%s,\n\"z\":%s\n}" % (new_x, new_y, new_z)
        self._output_device.put("printer/heads/0/position",
                                data,
                                onFinished=None)

    def homeBed(self, printer):
        self._output_device.put("printer/heads/0/position/z",
                                "0",
                                onFinished=None)

    def _onPreheatBedTimerFinished(self):
        self.setTargetBedTemperature(self._preheat_printer, 0)
        self._preheat_printer.updateIsPreheating(False)
        self._preheat_request_in_progress = True

    def cancelPreheatBed(self, printer: "PrinterOutputModel"):
        self.preheatBed(printer, temperature=0, duration=0)
        self._preheat_bed_timer.stop()
        printer.updateIsPreheating(False)

    def preheatBed(self, printer: "PrinterOutputModel", temperature, duration):
        try:
            temperature = round(
                temperature)  # The API doesn't allow floating point.
            duration = round(duration)
        except ValueError:
            return  # Got invalid values, can't pre-heat.

        if duration > 0:
            data = """{"temperature": "%i", "timeout": "%i"}""" % (temperature,
                                                                   duration)
        else:
            data = """{"temperature": "%i"}""" % temperature

        # Real bed pre-heating support is implemented from 3.5.92 and up.

        if Version(printer.firmwareVersion) < Version("3.5.92"):
            # No firmware-side duration support then, so just set target bed temp and set a timer.
            self.setTargetBedTemperature(printer, temperature=temperature)
            self._preheat_bed_timer.setInterval(duration * 1000)
            self._preheat_bed_timer.start()
            self._preheat_printer = printer
            printer.updateIsPreheating(True)
            return

        self._output_device.put("printer/bed/pre_heat",
                                data,
                                onFinished=self._onPutPreheatBedCompleted)
        printer.updateIsPreheating(True)
        self._preheat_request_in_progress = True
Exemplo n.º 44
0
class FishboneConnector(RepeaterDelegate):
    ConnectStatus = Enum('Status', "disconnected connecting connected")
    _status = ConnectStatus.disconnected

    statusChanged = pyqtSignal(int)

    @pyqtProperty(ConnectStatus, notify=statusChanged)
    def status(self):
        return self._status

    @status.setter
    def status(self, status):
        self._status = status
        self.statusChanged.emit(status.value)

    _program = None
    _device = None
    _screenWidth = 0
    _screenHeight = 0
    _screenBitDepth = 0

    @pyqtProperty(int)
    def screenWidth(self):
        return self._screenWidth

    @screenWidth.setter
    def screenWidth(self, value):
        self._screenWidth = value

    @pyqtProperty(int)
    def screenHeight(self):
        return self._screenHeight

    @screenHeight.setter
    def screenHeight(self, value):
        self._screenHeight = value

    @pyqtProperty(int)
    def screenBitDepth(self):
        return self._screenBitDepth

    @screenBitDepth.setter
    def screenBitDepth(self, value):
        self._screenBitDepth = value

    deviceChanged = pyqtSignal()

    @pyqtProperty(str, notify=deviceChanged)
    def device(self):
        return self._device

    @device.setter
    def device(self, device):
        if self._device != device:
            self._device = device
            self.deviceChanged.emit()

    def __init__(self, parent=None):
        super(FishboneConnector, self).__init__(parent)
        self._queryDeviceTimer = QTimer(self)
        self._queryDeviceTimer.setSingleShot(True)
        self._queryDeviceTimer.timeout.connect(self.onQueryDevice)
        self._queryDeviceTimeoutTimer = QTimer(self)
        self._queryDeviceTimeoutTimer.setSingleShot(True)
        self._queryDeviceTimeoutTimer.timeout.connect(
            self.onQeuryDeviceTimeout)
        self._launchDaemonTimeoutTimer = QTimer(self)
        self._launchDaemonTimeoutTimer.setSingleShot(True)
        self._launchDaemonTimeoutTimer.timeout.connect(self.launchDaemonFailed)
        self.onRepeaterStatusChanged()
        self._repeater.statusChanged.connect(self.onRepeaterStatusChanged)

    @pyqtSlot()
    def onRepeaterStatusChanged(self):
        self.status = self.ConnectStatus.connecting if self._repeater.isConnected(
        ) else self.ConnectStatus.disconnected
        if self.status == self.ConnectStatus.connecting:
            self._queryDeviceTimer.start(1000)
        elif self.status == self.ConnectStatus.disconnected:
            self._queryDeviceTimer.stop()
            self._queryDeviceTimeoutTimer.stop()

    @pyqtSlot()
    def onQueryDevice(self):
        self._queryDeviceTimeoutTimer.start(1000)
        block, ostream = self._repeater.getRequestBlock()
        ostream.writeQString('queryDevice')
        self._repeater.submitRequestBlock(block)

    def respQueryDevice(self, istream):
        self._queryDeviceTimeoutTimer.stop()
        self.program = istream.readQString()
        if self.program != 'daemon':
            self.startDaemon()
        else:
            self._launchDaemonTimeoutTimer.stop()
            version = istream.readQString()
            self.device = istream.readQString()
            self.screenWidth = istream.readInt()
            self.screenHeight = istream.readInt()
            self.screenBitDepth = istream.readInt()
            self.status = self.ConnectStatus.connected
            self._queryDeviceTimer.start()

    @pyqtSlot()
    def onQeuryDeviceTimeout(self):
        self.status = self.ConnectStatus.connecting
        self._queryDeviceTimer.start()

    def startDaemon(self):
        block, ostream = self._repeater.getRequestBlock()
        ostream.writeQString('startDaemon')
        with open("apps/bigeye-daemon", "rb") as f:
            ostream.writeBytes(f.read())
        self._repeater.submitRequestBlock(block)
        # start a start daemon timeout timer to detect daemon launched failed
        self._launchDaemonTimeoutTimer.start(3000)
        self._queryDeviceTimer.start()

    @pyqtSlot()
    def launchDaemonFailed(self):
        # what can I do with this situation
        print("daemon launched failed")

    def stopDaemon(self):
        block, ostream = self._repeater.getRequestBlock()
        ostream.writeQString('stopDaemon')
        self._repeater.submitRequestBlock(block)
Exemplo n.º 45
0
class TextItem(QGraphicsTextItem):
    DispatchSignalDelay = 50  # milliseconds

    def __init__(self, text=None, new=True):
        super().__init__(text)
        if new:
            self._editable = None
            self._editColor = QColor(Qt.yellow)
            self._colorSave = None
            self.setEditable(False)
            self.setupConnections()
            self._constraints = []
        self.setDefaultTextColor(QColor(Qt.darkMagenta))
        self.onTextChanged = []
        self.setFlags(self.ItemIsMovable | self.ItemIsFocusable
                      | self.ItemIsSelectable | self.ItemSendsGeometryChanges)
        self.pressedEventHandler = None
        self.onPositionChanged = None
        self._uid = uuid4()
        self._dispatchTimer = None

    def setupConnections(self):
        self.keyPressed = KeyPressedSignal()
        self.keyPressed.connect(self.dispatchOnTextChanged)
        self.mouseDoubleClickHandler = lambda event: self.setTextInteraction(
            True, select_all=True)
        self.setTextInteractionFlags(Qt.NoTextInteraction)
        # HACKFIX: gets rid of single-click selection rect around text when it first appears.  Now only shows selection rect when double-clicked:
        self.setTextInteraction(True)
        self.setTextInteraction(False)

    def dispatchOnTextChanged(self):
        if self._dispatchTimer:
            self._dispatchTimer.stop()
        self._dispatchTimer = QTimer()
        self._dispatchTimer.setInterval(self.DispatchSignalDelay)
        self._dispatchTimer.timeout.connect(self._dispatchOnTextChanged)
        self._dispatchTimer.setSingleShot(True)
        self._dispatchTimer.start()

    def _dispatchOnTextChanged(self):
        text = self.toPlainText()
        for slot in self.onTextChanged:
            slot(text)

    def uid(self):
        return self._uid

    def __setstate__(self, data):
        self.__init__(data['text'])
        self.setFlags(unpickleGfxItemFlags(data['flags']))
        self.setDefaultTextColor(data['color'])
        self._undoStack = data['undo stack']
        self.setPos(data['pos'])

    def __getstate__(self):
        return {
            'text': self.toPlainText(),
            'flags': int(self.flags()),
            'color': self.defaultTextColor(),
            'undo stack': self._undoStack,
            'pos': self.pos(),
        }

    def __deepcopy__(self, memo):
        copy = type(self)(new=False)
        copy._editable = self._editable
        copy._editColor = QColor(self._editColor)
        copy._colorSave = QColor(self._colorSave)
        copy.setPlainText(self.toPlainText())
        memo[id(self)] = copy
        copy.setFlags(self.flags())
        copy.setDefaultTextColor(self.defaultTextColor())
        copy._constraints = deepcopy(self._constraints, memo)
        copy.setPos(self.pos())
        copy.setupConnections()
        return copy

    def keyPressEvent(self, event):
        self.keyPressed.emit()
        super().keyPressEvent(event)

    def setUndoStack(self):
        return self._undoStack

    def focusOutEvent(self, event):
        #self.setTextInteraction(False)
        super().focusOutEvent(event)

    def setPlainText(self, text):
        if self.toPlainText() != text:
            super().setPlainText(text)
            if self.onTextChanged:
                for slot in self.onTextChanged:
                    slot(text)

    def setTextInteraction(self, state, select_all=True):
        text = self.toPlainText()
        if self.editable() and state and self.textInteractionFlags(
        ) == Qt.NoTextInteraction:
            # switch on editor mode:
            self.setTextInteractionFlags(Qt.TextEditorInteraction)
            # manually do what a mouse click would do else:
            self.setFocus(Qt.MouseFocusReason)  # give the item keyboard focus
            self.setSelected(
                True
            )  # ensure itemChange() gets called when we click out of the item
            if select_all:  # option to select the whole text (e.g. after creation of the TextItem)
                c = self.textCursor()
                c.select(QTextCursor.WordUnderCursor)
                self.setTextCursor(c)
        elif not state and self.textInteractionFlags(
        ) == Qt.TextEditorInteraction:
            # turn off editor mode:
            self.setTextInteractionFlags(Qt.NoTextInteraction)
            # deselect text (else it keeps gray shade):
            c = self.textCursor()
            c.clearSelection()
            self.setTextCursor(c)
            self.clearFocus()

    def mouseDoubleClickEvent(self, event):
        if self.mouseDoubleClickHandler:
            self.mouseDoubleClickHandler(event)
        else:
            super().mouseDoubleClickEvent(event)

    def mousePressEvent(self, event):
        if self.pressedEventHandler:
            self.pressedEventHandler(event)
        super().mousePressEvent(event)

    def itemChange(self, change, value):
        if change == self.ItemSelectedChange:
            if self.textInteractionFlags(
            ) != Qt.NoTextInteraction and not value:
                # item received SelectedChange event AND is in editor mode AND is about to be deselected
                self.setTextInteraction(False)  # leave editor mode
        elif change == self.ItemPositionChange:
            if self.onPositionChanged:
                self.onPositionChanged()
        return super().itemChange(change, value)

    def paint(self, painter, option, widget):
        painter.setRenderHints(QPainter.HighQualityAntialiasing
                               | QPainter.Antialiasing
                               | QPainter.TextAntialiasing)
        super().paint(painter, option, widget)

    def setSelected(self, selected):
        super().setSelected(selected)
        if selected:
            if self.parentItem():
                self.parentItem().setSelected(False)

    def delete(self, deleted=None):
        if deleted is None:
            deleted = {}
        deleted["parent"] = self.parentItem()
        deleted["scene"] = self.scene()
        self.setParentItem(None)
        if self.scene():
            self.scene().removeItem(self)
        return deleted

    def undelete(self, deleted):
        self.setParentItem(deleted["parent"])
        if deleted["scene"]:
            deleted["scene"].addItem(self)

    def setEditable(self, editable):
        if self._editable != editable:
            self._editable = editable
            if editable:
                self._colorSave = self.color()
                self.setColor(self._editColor)
            else:
                if self._colorSave:
                    self.setColor(self._colorSave)
                self._colorSave = None
            self.update()

    def setEditColor(self, color):
        self._editColor = color
        self.update()

    def editable(self):
        return self._editable

    def setColor(self, color):
        self.setDefaultTextColor(color)

    def color(self):
        return self.defaultTextColor()

    def constraints(self):
        return self._constraints

    def isConstrained(self):
        return self._constraints != []

    def addConstraint(self, constraint):
        self._constraints.append(constraint)
Exemplo n.º 46
0
 def export(self):
     timer = QTimer(self.view)
     timer.setSingleShot(True)
     timer.timeout.connect(self.run)
     timer.start()
Exemplo n.º 47
0
class DownloadsPage(QWidget):
    """
    This class is responsible for managing all items on the downloads page.
    The downloads page shows all downloads and specific details about a download.
    """
    received_downloads = pyqtSignal(object)

    def __init__(self):
        QWidget.__init__(self)
        self.export_dir = None
        self.filter = DOWNLOADS_FILTER_ALL
        self.download_widgets = {}  # key: infohash, value: QTreeWidgetItem
        self.downloads = None
        self.downloads_timer = QTimer()
        self.downloads_timeout_timer = QTimer()
        self.downloads_last_update = 0
        self.selected_item = None
        self.dialog = None
        self.downloads_request_mgr = TriblerRequestManager()
        self.request_mgr = None

    def showEvent(self, QShowEvent):
        """
        When the downloads tab is clicked, we want to update the downloads list immediately.
        """
        super(DownloadsPage, self).showEvent(QShowEvent)
        self.stop_loading_downloads()
        self.schedule_downloads_timer(True)

    def initialize_downloads_page(self):
        self.window().downloads_tab.initialize()
        self.window().downloads_tab.clicked_tab_button.connect(
            self.on_downloads_tab_button_clicked)

        self.window().start_download_button.clicked.connect(
            self.on_start_download_clicked)
        self.window().stop_download_button.clicked.connect(
            self.on_stop_download_clicked)
        self.window().remove_download_button.clicked.connect(
            self.on_remove_download_clicked)
        self.window().play_download_button.clicked.connect(
            self.on_play_download_clicked)

        self.window().downloads_list.itemSelectionChanged.connect(
            self.on_download_item_clicked)

        self.window().downloads_list.customContextMenuRequested.connect(
            self.on_right_click_item)

        self.window().download_details_widget.initialize_details_widget()
        self.window().download_details_widget.hide()

        self.window().downloads_filter_input.textChanged.connect(
            self.on_filter_text_changed)

        self.window().downloads_list.header().resizeSection(12, 146)

        if not self.window().vlc_available:
            self.window().play_download_button.setHidden(True)

    def on_filter_text_changed(self, text):
        self.window().downloads_list.clearSelection()
        self.window().download_details_widget.hide()
        self.update_download_visibility()

    def start_loading_downloads(self):
        self.schedule_downloads_timer(now=True)

    def schedule_downloads_timer(self, now=False):
        self.downloads_timer = QTimer()
        self.downloads_timer.setSingleShot(True)
        self.downloads_timer.timeout.connect(self.load_downloads)
        self.downloads_timer.start(0 if now else 1000)

        self.downloads_timeout_timer = QTimer()
        self.downloads_timeout_timer.setSingleShot(True)
        self.downloads_timeout_timer.timeout.connect(
            self.on_downloads_request_timeout)
        self.downloads_timeout_timer.start(16000)

    def on_downloads_request_timeout(self):
        self.downloads_request_mgr.cancel_request()
        self.schedule_downloads_timer()

    def stop_loading_downloads(self):
        self.downloads_timer.stop()
        self.downloads_timeout_timer.stop()

    def load_downloads(self):
        url = "downloads?get_pieces=1"
        if self.window().download_details_widget.currentIndex() == 3:
            url += "&get_peers=1"
        elif self.window().download_details_widget.currentIndex() == 1:
            url += "&get_files=1"

        if not self.isHidden() or (time.time() - self.downloads_last_update >
                                   30):
            # Update if the downloads page is visible or if we haven't updated for longer than 30 seconds
            self.downloads_last_update = time.time()
            priority = "LOW" if self.isHidden() else "HIGH"
            self.downloads_request_mgr.cancel_request()
            self.downloads_request_mgr = TriblerRequestManager()
            self.downloads_request_mgr.perform_request(
                url, self.on_received_downloads, priority=priority)

    def on_received_downloads(self, downloads):
        if not downloads:
            return  # This might happen when closing Tribler

        total_download = 0
        total_upload = 0
        self.received_downloads.emit(downloads)
        self.downloads = downloads

        download_infohashes = set()

        items = []
        for download in downloads["downloads"]:
            if download["infohash"] in self.download_widgets:
                item = self.download_widgets[download["infohash"]]
            else:
                item = DownloadWidgetItem()
                self.download_widgets[download["infohash"]] = item
                items.append(item)

            item.update_with_download(download)

            # Update video player with download info
            video_infohash = self.window().video_player_page.active_infohash
            if video_infohash != "" and download["infohash"] == video_infohash:
                self.window().video_player_page.update_with_download_info(
                    download)

            total_download += download["speed_down"]
            total_upload += download["speed_up"]

            download_infohashes.add(download["infohash"])

            if self.window().download_details_widget.current_download is not None and \
                    self.window().download_details_widget.current_download["infohash"] == download["infohash"]:
                self.window(
                ).download_details_widget.current_download = download
                self.window().download_details_widget.update_pages()

        self.window().downloads_list.addTopLevelItems(items)
        for item in items:
            self.window().downloads_list.setItemWidget(item, 2,
                                                       item.bar_container)

        # Check whether there are download that should be removed
        for infohash, item in self.download_widgets.items():
            if infohash not in download_infohashes:
                index = self.window().downloads_list.indexOfTopLevelItem(item)
                self.window().downloads_list.takeTopLevelItem(index)
                del self.download_widgets[infohash]

        if self.window().tray_icon:
            self.window().tray_icon.setToolTip(
                "Down: %s, Up: %s" %
                (format_speed(total_download), format_speed(total_upload)))
        self.update_download_visibility()
        self.schedule_downloads_timer()

        # Update the top download management button if we have a row selected
        if len(self.window().downloads_list.selectedItems()) > 0:
            self.on_download_item_clicked()

    def update_download_visibility(self):
        for i in range(self.window().downloads_list.topLevelItemCount()):
            item = self.window().downloads_list.topLevelItem(i)
            filter_match = self.window().downloads_filter_input.text().lower(
            ) in item.download_info["name"].lower()
            is_creditmining = item.download_info["credit_mining"]
            if self.filter == DOWNLOADS_FILTER_CREDITMINING:
                item.setHidden(not is_creditmining or not filter_match)
            else:
                item.setHidden(not item.get_raw_download_status() in DOWNLOADS_FILTER_DEFINITION[self.filter] or \
                               not filter_match or is_creditmining)

    def on_downloads_tab_button_clicked(self, button_name):
        if button_name == "downloads_all_button":
            self.filter = DOWNLOADS_FILTER_ALL
        elif button_name == "downloads_downloading_button":
            self.filter = DOWNLOADS_FILTER_DOWNLOADING
        elif button_name == "downloads_completed_button":
            self.filter = DOWNLOADS_FILTER_COMPLETED
        elif button_name == "downloads_active_button":
            self.filter = DOWNLOADS_FILTER_ACTIVE
        elif button_name == "downloads_inactive_button":
            self.filter = DOWNLOADS_FILTER_INACTIVE
        elif button_name == "downloads_creditmining_button":
            self.filter = DOWNLOADS_FILTER_CREDITMINING

        self.window().downloads_list.clearSelection()
        self.window().download_details_widget.hide()
        self.update_download_visibility()

    @staticmethod
    def start_download_enabled(download_widget):
        return download_widget.get_raw_download_status() == DLSTATUS_STOPPED

    @staticmethod
    def stop_download_enabled(download_widget):
        status = download_widget.get_raw_download_status()
        return status != DLSTATUS_STOPPED and status != DLSTATUS_STOPPED_ON_ERROR

    @staticmethod
    def force_recheck_download_enabled(download_widget):
        status = download_widget.get_raw_download_status()
        return status != DLSTATUS_METADATA and status != DLSTATUS_HASHCHECKING and status != DLSTATUS_WAITING4HASHCHECK

    def on_download_item_clicked(self):
        self.window().download_details_widget.show()
        if len(self.window().downloads_list.selectedItems()) == 0:
            self.window().play_download_button.setEnabled(False)
            self.window().remove_download_button.setEnabled(False)
            self.window().start_download_button.setEnabled(False)
            self.window().stop_download_button.setEnabled(False)
            return

        self.selected_item = self.window().downloads_list.selectedItems()[0]
        self.window().play_download_button.setEnabled(True)
        self.window().remove_download_button.setEnabled(True)
        self.window().start_download_button.setEnabled(
            DownloadsPage.start_download_enabled(self.selected_item))
        self.window().stop_download_button.setEnabled(
            DownloadsPage.stop_download_enabled(self.selected_item))

        self.window().download_details_widget.update_with_download(
            self.selected_item.download_info)

    def on_start_download_clicked(self):
        infohash = self.selected_item.download_info["infohash"]
        self.request_mgr = TriblerRequestManager()
        self.request_mgr.perform_request("downloads/%s" % infohash,
                                         self.on_download_resumed,
                                         method='PATCH',
                                         data="state=resume")

    def on_download_resumed(self, json_result):
        if json_result and 'modified' in json_result:
            self.selected_item.download_info['status'] = "DLSTATUS_DOWNLOADING"
            self.selected_item.update_item()
            self.on_download_item_clicked()

    def on_stop_download_clicked(self):
        infohash = self.selected_item.download_info["infohash"]
        self.request_mgr = TriblerRequestManager()
        self.request_mgr.perform_request("downloads/%s" % infohash,
                                         self.on_download_stopped,
                                         method='PATCH',
                                         data="state=stop")

    def on_play_download_clicked(self):
        self.window().left_menu_button_video_player.click()
        self.window().video_player_page.set_torrent_infohash(
            self.selected_item.download_info["infohash"])
        self.window().left_menu_playlist.set_loading()

    def on_download_stopped(self, json_result):
        if json_result and "modified" in json_result:
            self.selected_item.download_info['status'] = "DLSTATUS_STOPPED"
            self.selected_item.update_item()
            self.on_download_item_clicked()

    def on_remove_download_clicked(self):
        self.dialog = ConfirmationDialog(
            self, "Remove download",
            "Are you sure you want to remove this download?",
            [('remove download', BUTTON_TYPE_NORMAL),
             ('remove download + data', BUTTON_TYPE_NORMAL),
             ('cancel', BUTTON_TYPE_CONFIRM)])
        self.dialog.button_clicked.connect(self.on_remove_download_dialog)
        self.dialog.show()

    def on_remove_download_dialog(self, action):
        if action != 2:
            infohash = self.selected_item.download_info["infohash"]

            # Reset video player if necessary before doing the actual request
            if self.window().video_player_page.active_infohash == infohash:
                self.window().video_player_page.reset_player()

            self.request_mgr = TriblerRequestManager()
            self.request_mgr.perform_request("downloads/%s" % infohash,
                                             self.on_download_removed,
                                             method='DELETE',
                                             data="remove_data=%d" % action)

        self.dialog.setParent(None)
        self.dialog = None

    def on_download_removed(self, json_result):
        if json_result and "removed" in json_result:
            infohash = self.selected_item.download_info["infohash"]
            index = self.window().downloads_list.indexOfTopLevelItem(
                self.selected_item)
            self.window().downloads_list.takeTopLevelItem(index)
            if infohash in self.download_widgets:  # Could have been removed already through API
                del self.download_widgets[infohash]
            self.window().download_details_widget.hide()

    def on_force_recheck_download(self):
        infohash = self.selected_item.download_info["infohash"]
        self.request_mgr = TriblerRequestManager()
        self.request_mgr.perform_request("downloads/%s" % infohash,
                                         self.on_forced_recheck,
                                         method='PATCH',
                                         data='state=recheck')

    def on_forced_recheck(self, result):
        if result and "modified" in result:
            self.selected_item.download_info[
                'status'] = "DLSTATUS_HASHCHECKING"
            self.selected_item.update_item()
            self.on_download_item_clicked()

    def change_anonymity(self, hops):
        infohash = self.selected_item.download_info["infohash"]
        self.request_mgr = TriblerRequestManager()
        self.request_mgr.perform_request("downloads/%s" % infohash,
                                         lambda _: None,
                                         method='PATCH',
                                         data='anon_hops=%d' % hops)

    def on_explore_files(self):
        path = os.path.normpath(
            os.path.join(
                self.window().tribler_settings['downloadconfig']['saveas'],
                self.selected_item.download_info["destination"]))
        QDesktopServices.openUrl(QUrl.fromLocalFile(path))

    def on_export_download(self):
        self.export_dir = QFileDialog.getExistingDirectory(
            self, "Please select the destination directory", "",
            QFileDialog.ShowDirsOnly)

        if len(self.export_dir) > 0:
            # Show confirmation dialog where we specify the name of the file
            torrent_name = self.selected_item.download_info['name']
            self.dialog = ConfirmationDialog(
                self,
                "Export torrent file",
                "Please enter the name of the torrent file:",
                [('SAVE', BUTTON_TYPE_NORMAL),
                 ('CANCEL', BUTTON_TYPE_CONFIRM)],
                show_input=True)
            self.dialog.dialog_widget.dialog_input.setPlaceholderText(
                'Torrent file name')
            self.dialog.dialog_widget.dialog_input.setText("%s.torrent" %
                                                           torrent_name)
            self.dialog.dialog_widget.dialog_input.setFocus()
            self.dialog.button_clicked.connect(
                self.on_export_download_dialog_done)
            self.dialog.show()

    def on_export_download_dialog_done(self, action):
        if action == 0:
            filename = self.dialog.dialog_widget.dialog_input.text()
            self.request_mgr = TriblerRequestManager()
            self.request_mgr.download_file(
                "downloads/%s/torrent" %
                self.selected_item.download_info['infohash'],
                lambda data: self.on_export_download_request_done(
                    filename, data))

        self.dialog.setParent(None)
        self.dialog = None

    def on_export_download_request_done(self, filename, data):
        dest_path = os.path.join(self.export_dir, filename)
        try:
            torrent_file = open(dest_path, "wb")
            torrent_file.write(data)
            torrent_file.close()
        except IOError as exc:
            ConfirmationDialog.show_error(
                self.window(), "Error when exporting file",
                "An error occurred when exporting the torrent file: %s" %
                str(exc))
        else:
            if self.window().tray_icon:
                self.window().tray_icon.showMessage(
                    "Torrent file exported",
                    "Torrent file exported to %s" % dest_path)

    def on_right_click_item(self, pos):
        item_clicked = self.window().downloads_list.itemAt(pos)
        if not item_clicked:
            return

        self.selected_item = item_clicked

        menu = TriblerActionMenu(self)

        start_action = QAction('Start', self)
        stop_action = QAction('Stop', self)
        remove_download_action = QAction('Remove download', self)
        force_recheck_action = QAction('Force recheck', self)
        export_download_action = QAction('Export .torrent file', self)
        explore_files_action = QAction('Explore files', self)

        no_anon_action = QAction('No anonymity', self)
        one_hop_anon_action = QAction('One hop', self)
        two_hop_anon_action = QAction('Two hops', self)
        three_hop_anon_action = QAction('Three hops', self)

        start_action.triggered.connect(self.on_start_download_clicked)
        start_action.setEnabled(
            DownloadsPage.start_download_enabled(self.selected_item))
        stop_action.triggered.connect(self.on_stop_download_clicked)
        stop_action.setEnabled(
            DownloadsPage.stop_download_enabled(self.selected_item))
        remove_download_action.triggered.connect(
            self.on_remove_download_clicked)
        force_recheck_action.triggered.connect(self.on_force_recheck_download)
        force_recheck_action.setEnabled(
            DownloadsPage.force_recheck_download_enabled(self.selected_item))
        export_download_action.triggered.connect(self.on_export_download)
        explore_files_action.triggered.connect(self.on_explore_files)

        no_anon_action.triggered.connect(lambda: self.change_anonymity(0))
        one_hop_anon_action.triggered.connect(lambda: self.change_anonymity(1))
        two_hop_anon_action.triggered.connect(lambda: self.change_anonymity(2))
        three_hop_anon_action.triggered.connect(
            lambda: self.change_anonymity(3))

        menu.addAction(start_action)
        menu.addAction(stop_action)

        if self.window().vlc_available:
            play_action = QAction('Play', self)
            play_action.triggered.connect(self.on_play_download_clicked)
            menu.addAction(play_action)
        menu.addSeparator()
        menu.addAction(remove_download_action)
        menu.addSeparator()
        menu.addAction(force_recheck_action)
        menu.addSeparator()
        menu.addAction(export_download_action)
        menu.addSeparator()
        menu_anon_level = menu.addMenu("Change anonymity")
        menu_anon_level.addAction(no_anon_action)
        menu_anon_level.addAction(one_hop_anon_action)
        menu_anon_level.addAction(two_hop_anon_action)
        menu_anon_level.addAction(three_hop_anon_action)
        menu.addAction(explore_files_action)

        menu.exec_(self.window().downloads_list.mapToGlobal(pos))
class CloudOutputDeviceManager:

    META_CLUSTER_ID = "um_cloud_cluster_id"
    META_NETWORK_KEY = "um_network_key"

    # The interval with which the remote clusters are checked
    CHECK_CLUSTER_INTERVAL = 30.0  # seconds

    # The translation catalog for this device.
    I18N_CATALOG = i18nCatalog("cura")

    # Signal emitted when the list of discovered devices changed.
    discoveredDevicesChanged = Signal()

    def __init__(self) -> None:
        # Persistent dict containing the remote clusters for the authenticated user.
        self._remote_clusters = {}  # type: Dict[str, CloudOutputDevice]
        self._account = CuraApplication.getInstance().getCuraAPI().account  # type: Account
        self._api = CloudApiClient(self._account, on_error = lambda error: Logger.log("e", str(error)))
        self._account.loginStateChanged.connect(self._onLoginStateChanged)

        # Create a timer to update the remote cluster list
        self._update_timer = QTimer()
        self._update_timer.setInterval(int(self.CHECK_CLUSTER_INTERVAL * 1000))
        self._update_timer.setSingleShot(False)
        self._update_timer.timeout.connect(self._getRemoteClusters)

        # Ensure we don't start twice.
        self._running = False

    ## Starts running the cloud output device manager, thus periodically requesting cloud data.
    def start(self):
        if self._running:
            return
        if not self._account.isLoggedIn:
            return
        self._running = True
        if not self._update_timer.isActive():
            self._update_timer.start()
        self._getRemoteClusters()

    ## Stops running the cloud output device manager.
    def stop(self):
        if not self._running:
            return
        self._running = False
        if self._update_timer.isActive():
            self._update_timer.stop()
        self._onGetRemoteClustersFinished([])  # Make sure we remove all cloud output devices.

    ## Force refreshing connections.
    def refreshConnections(self) -> None:
        self._connectToActiveMachine()

    ## Called when the uses logs in or out
    def _onLoginStateChanged(self, is_logged_in: bool) -> None:
        if is_logged_in:
            self.start()
        else:
            self.stop()

    ## Gets all remote clusters from the API.
    def _getRemoteClusters(self) -> None:
        self._api.getClusters(self._onGetRemoteClustersFinished)

    ## Callback for when the request for getting the clusters is finished.
    def _onGetRemoteClustersFinished(self, clusters: List[CloudClusterResponse]) -> None:
        online_clusters = {c.cluster_id: c for c in clusters if c.is_online}  # type: Dict[str, CloudClusterResponse]
        for device_id, cluster_data in online_clusters.items():
            if device_id not in self._remote_clusters:
                self._onDeviceDiscovered(cluster_data)
            else:
                self._onDiscoveredDeviceUpdated(cluster_data)

        removed_device_keys = set(self._remote_clusters.keys()) - set(online_clusters.keys())
        for device_id in removed_device_keys:
            self._onDiscoveredDeviceRemoved(device_id)

    def _onDeviceDiscovered(self, cluster_data: CloudClusterResponse) -> None:
        device = CloudOutputDevice(self._api, cluster_data)
        CuraApplication.getInstance().getDiscoveredPrintersModel().addDiscoveredPrinter(
            ip_address=device.key,
            key=device.getId(),
            name=device.getName(),
            create_callback=self._createMachineFromDiscoveredDevice,
            machine_type=device.printerType,
            device=device
        )
        self._remote_clusters[device.getId()] = device
        self.discoveredDevicesChanged.emit()
        self._connectToActiveMachine()

    def _onDiscoveredDeviceUpdated(self, cluster_data: CloudClusterResponse) -> None:
        device = self._remote_clusters.get(cluster_data.cluster_id)
        if not device:
            return
        CuraApplication.getInstance().getDiscoveredPrintersModel().updateDiscoveredPrinter(
            ip_address=device.key,
            name=cluster_data.friendly_name,
            machine_type=device.printerType
        )
        self.discoveredDevicesChanged.emit()

    def _onDiscoveredDeviceRemoved(self, device_id: str) -> None:
        device = self._remote_clusters.pop(device_id, None)  # type: Optional[CloudOutputDevice]
        if not device:
            return
        device.close()
        CuraApplication.getInstance().getDiscoveredPrintersModel().removeDiscoveredPrinter(device.key)
        output_device_manager = CuraApplication.getInstance().getOutputDeviceManager()
        if device.key in output_device_manager.getOutputDeviceIds():
            output_device_manager.removeOutputDevice(device.key)
        self.discoveredDevicesChanged.emit()

    def _createMachineFromDiscoveredDevice(self, key: str) -> None:
        device = self._remote_clusters[key]
        if not device:
            return

        # Create a new machine and activate it.
        # We do not use use MachineManager.addMachine here because we need to set the cluster ID before activating it.
        new_machine = CuraStackBuilder.createMachine(device.name, device.printerType)
        if not new_machine:
            Logger.log("e", "Failed creating a new machine")
            return
        new_machine.setMetaDataEntry(self.META_CLUSTER_ID, device.key)
        CuraApplication.getInstance().getMachineManager().setActiveMachine(new_machine.getId())
        self._connectToOutputDevice(device, new_machine)

    ##  Callback for when the active machine was changed by the user or a new remote cluster was found.
    def _connectToActiveMachine(self) -> None:
        active_machine = CuraApplication.getInstance().getGlobalContainerStack()
        if not active_machine:
            return

        output_device_manager = CuraApplication.getInstance().getOutputDeviceManager()
        stored_cluster_id = active_machine.getMetaDataEntry(self.META_CLUSTER_ID)
        local_network_key = active_machine.getMetaDataEntry(self.META_NETWORK_KEY)
        for device in self._remote_clusters.values():
            if device.key == stored_cluster_id:
                # Connect to it if the stored ID matches.
                self._connectToOutputDevice(device, active_machine)
            elif local_network_key and device.matchesNetworkKey(local_network_key):
                # Connect to it if we can match the local network key that was already present.
                self._connectToOutputDevice(device, active_machine)
            elif device.key in output_device_manager.getOutputDeviceIds():
                # Remove device if it is not meant for the active machine.
                output_device_manager.removeOutputDevice(device.key)

    ## Connects to an output device and makes sure it is registered in the output device manager.
    def _connectToOutputDevice(self, device: CloudOutputDevice, machine: GlobalStack) -> None:
        machine.setName(device.name)
        machine.setMetaDataEntry(self.META_CLUSTER_ID, device.key)
        machine.setMetaDataEntry("group_name", device.name)
        machine.addConfiguredConnectionType(device.connectionType.value)

        if not device.isConnected():
            device.connect()

        output_device_manager = CuraApplication.getInstance().getOutputDeviceManager()
        if device.key not in output_device_manager.getOutputDeviceIds():
            output_device_manager.addOutputDevice(device)
Exemplo n.º 49
0
class Completer(QObject):

    """Completer which manages completions in a CompletionView.

    Attributes:
        _cmd: The statusbar Command object this completer belongs to.
        _ignore_change: Whether to ignore the next completion update.
        _win_id: The window ID this completer is in.
        _timer: The timer used to trigger the completion update.
        _cursor_part: The cursor part index for the next completion update.
        _last_cursor_pos: The old cursor position so we avoid double completion
                          updates.
        _last_text: The old command text so we avoid double completion updates.
    """

    def __init__(self, cmd, win_id, parent=None):
        super().__init__(parent)
        self._win_id = win_id
        self._cmd = cmd
        self._cmd.update_completion.connect(self.schedule_completion_update)
        self._cmd.textEdited.connect(self.on_text_edited)
        self._ignore_change = False
        self._empty_item_idx = None
        self._timer = QTimer()
        self._timer.setSingleShot(True)
        self._timer.setInterval(0)
        self._timer.timeout.connect(self.update_completion)
        self._cursor_part = None
        self._last_cursor_pos = None
        self._last_text = None

    def __repr__(self):
        return utils.get_repr(self)

    def _model(self):
        """Convienience method to get the current completion model."""
        completion = objreg.get('completion', scope='window',
                                window=self._win_id)
        return completion.model()

    def _get_completion_model(self, completion, parts, cursor_part):
        """Get a completion model based on an enum member.

        Args:
            completion: An usertypes.Completion member.
            parts: The parts currently in the commandline.
            cursor_part: The part the cursor is in.

        Return:
            A completion model.
        """
        if completion == usertypes.Completion.option:
            section = parts[cursor_part - 1]
            model = instances.get(completion).get(section)
        elif completion == usertypes.Completion.value:
            section = parts[cursor_part - 2]
            option = parts[cursor_part - 1]
            try:
                model = instances.get(completion)[section][option]
            except KeyError:
                # No completion model for this section/option.
                model = None
        else:
            model = instances.get(completion)
        return model

    def _filter_cmdline_parts(self, parts, cursor_part):
        """Filter a list of commandline parts to exclude flags.

        Args:
            parts: A list of parts.
            cursor_part: The index of the part the cursor is over.

        Return:
            A (parts, cursor_part) tuple with the modified values.
        """
        if parts == ['']:
            # Empty commandline, i.e. only :.
            return [''], 0
        filtered_parts = []
        for i, part in enumerate(parts):
            if part == '--':
                break
            elif part.startswith('-'):
                if cursor_part >= i:
                    cursor_part -= 1
            else:
                filtered_parts.append(part)
        return filtered_parts, cursor_part

    def _get_new_completion(self, parts, cursor_part):
        """Get a new completion.

        Args:
            parts: The command chunks to get a completion for.
            cursor_part: The part the cursor is over currently.

        Return:
            A completion model.
        """
        try:
            if parts[cursor_part].startswith('-'):
                # cursor on a flag
                return
        except IndexError:
            pass
        log.completion.debug("Before filtering flags: parts {}, cursor_part "
                             "{}".format(parts, cursor_part))
        parts, cursor_part = self._filter_cmdline_parts(parts, cursor_part)
        log.completion.debug("After filtering flags: parts {}, cursor_part "
                             "{}".format(parts, cursor_part))
        if cursor_part == 0:
            # '|' or 'set|'
            return instances.get(usertypes.Completion.command)
        # delegate completion to command
        try:
            completions = cmdutils.cmd_dict[parts[0]].completion
        except KeyError:
            # entering an unknown command
            return None
        if completions is None:
            # command without any available completions
            return None
        dbg_completions = [c.name for c in completions]
        try:
            idx = cursor_part - 1
            completion = completions[idx]
        except IndexError:
            # More arguments than completions
            log.completion.debug("completions: {}".format(
                ', '.join(dbg_completions)))
            return None
        dbg_completions[idx] = '*' + dbg_completions[idx] + '*'
        log.completion.debug("completions: {}".format(
            ', '.join(dbg_completions)))
        model = self._get_completion_model(completion, parts, cursor_part)
        return model

    def _quote(self, s):
        """Quote s if it needs quoting for the commandline.

        Note we don't use shlex.quote because that quotes a lot of shell
        metachars we don't need to have quoted.
        """
        if not s:
            return "''"
        elif any(c in s for c in ' \'\t\n\\'):
            # use single quotes, and put single quotes into double quotes
            # the string $'b is then quoted as '$'"'"'b'
            return "'" + s.replace("'", "'\"'\"'") + "'"
        else:
            return s

    def selection_changed(self, selected, _deselected):
        """Change the completed part if a new item was selected.

        Called from the views selectionChanged method.

        Args:
            selected: New selection.
            _delected: Previous selection.
        """
        indexes = selected.indexes()
        if not indexes:
            return
        model = self._model()
        data = model.data(indexes[0])
        if data is None:
            return
        parts = self.split()
        try:
            needs_quoting = cmdutils.cmd_dict[parts[0]].maxsplit is None
        except KeyError:
            needs_quoting = True
        if needs_quoting:
            data = self._quote(data)
        if model.count() == 1 and config.get('completion', 'quick-complete'):
            # If we only have one item, we want to apply it immediately
            # and go on to the next part.
            self.change_completed_part(data, immediate=True)
        else:
            log.completion.debug("Will ignore next completion update.")
            self._ignore_change = True
            self.change_completed_part(data)

    @pyqtSlot()
    def schedule_completion_update(self):
        """Schedule updating/enabling completion.

        For performance reasons we don't want to block here, instead we do this
        in the background.
        """
        if (self._cmd.cursorPosition() == self._last_cursor_pos and
                self._cmd.text() == self._last_text):
            log.completion.debug("Ignoring update because there were no "
                                 "changes.")
        else:
            log.completion.debug("Scheduling completion update.")
            self._timer.start()
        self._last_cursor_pos = self._cmd.cursorPosition()
        self._last_text = self._cmd.text()

    @pyqtSlot()
    def update_completion(self):
        """Check if completions are available and activate them."""
        self.update_cursor_part()
        parts = self.split()

        log.completion.debug(
            "Updating completion - prefix {}, parts {}, cursor_part {}".format(
                self._cmd.prefix(), parts, self._cursor_part))

        if self._ignore_change:
            log.completion.debug("Ignoring completion update because "
                                 "ignore_change is True.")
            self._ignore_change = False
            return

        completion = objreg.get('completion', scope='window',
                                window=self._win_id)

        if self._cmd.prefix() != ':':
            # This is a search or gibberish, so we don't need to complete
            # anything (yet)
            # FIXME complete searches
            # https://github.com/The-Compiler/qutebrowser/issues/32
            completion.hide()
            return

        model = self._get_new_completion(parts, self._cursor_part)

        if model != self._model():
            if model is None:
                completion.hide()
            else:
                completion.set_model(model)

        if model is None:
            log.completion.debug("No completion model for {}.".format(parts))
            return

        try:
            pattern = parts[self._cursor_part].strip()
        except IndexError:
            pattern = ''
        self._model().set_pattern(pattern)

        log.completion.debug(
            "New completion for {}: {}, with pattern '{}'".format(
                parts, model.srcmodel.__class__.__name__, pattern))

        if self._model().count() == 0:
            completion.hide()
            return

        if completion.enabled:
            completion.show()

    def split(self, keep=False, aliases=False):
        """Get the text split up in parts.

        Args:
            keep: Whether to keep special chars and whitespace.
            aliases: Whether to resolve aliases.
        """
        text = self._cmd.text()[len(self._cmd.prefix()):]
        if not text:
            # When only ":" is entered, we already have one imaginary part,
            # which just is empty at the moment.
            return ['']
        if not text.strip():
            # Text is only whitespace so we treat this as a single element with
            # the whitespace.
            return [text]
        runner = runners.CommandRunner(self._win_id)
        result = runner.parse(text, fallback=True, aliases=aliases, keep=keep)
        parts = result.cmdline
        if self._empty_item_idx is not None:
            log.completion.debug("Empty element queued at {}, "
                                 "inserting.".format(self._empty_item_idx))
            parts.insert(self._empty_item_idx, '')
        #log.completion.debug("Splitting '{}' -> {}".format(text, parts))
        return parts

    @pyqtSlot()
    def update_cursor_part(self):
        """Get the part index of the commandline where the cursor is over."""
        cursor_pos = self._cmd.cursorPosition()
        snippet = slice(cursor_pos - 1, cursor_pos + 1)
        if self._cmd.text()[snippet] == '  ':
            spaces = True
        else:
            spaces = False
        cursor_pos -= len(self._cmd.prefix())
        parts = self.split(keep=True)
        log.completion.vdebug(
            "text: {}, parts: {}, cursor_pos after removing prefix '{}': "
            "{}".format(self._cmd.text(), parts, self._cmd.prefix(),
                        cursor_pos))
        skip = 0
        for i, part in enumerate(parts):
            log.completion.vdebug("Checking part {}: {}".format(i, parts[i]))
            if not part:
                skip += 1
                continue
            if cursor_pos <= len(part):
                # foo| bar
                self._cursor_part = i - skip
                if spaces:
                    self._empty_item_idx = i - skip
                else:
                    self._empty_item_idx = None
                log.completion.vdebug("cursor_pos {} <= len(part) {}, "
                                      "setting cursor_part {} - {} (skip), "
                                      "empty_item_idx {}".format(
                                          cursor_pos, len(part), i, skip,
                                          self._empty_item_idx))
                break
            cursor_pos -= len(part)
            log.completion.vdebug(
                "Removing len({!r}) -> {} from cursor_pos -> {}".format(
                    part, len(part), cursor_pos))
        else:
            self._cursor_part = i - skip
            if spaces:
                self._empty_item_idx = i - skip
            else:
                self._empty_item_idx = None
        log.completion.debug("cursor_part {}, spaces {}".format(
            self._cursor_part, spaces))
        return

    def change_completed_part(self, newtext, immediate=False):
        """Change the part we're currently completing in the commandline.

        Args:
            text: The text to set (string).
            immediate: True if the text should be completed immediately
                       including a trailing space and we shouldn't continue
                       completing the current item.
        """
        parts = self.split()
        log.completion.debug("changing part {} to '{}'".format(
            self._cursor_part, newtext))
        try:
            parts[self._cursor_part] = newtext
        except IndexError:
            parts.append(newtext)
        # We want to place the cursor directly after the part we just changed.
        cursor_str = self._cmd.prefix() + ' '.join(
            parts[:self._cursor_part + 1])
        if immediate:
            # If we should complete immediately, we want to move the cursor by
            # one more char, to get to the next field.
            cursor_str += ' '
        text = self._cmd.prefix() + ' '.join(parts)
        if immediate and self._cursor_part == len(parts) - 1:
            # If we should complete immediately and we're completing the last
            # part in the commandline, we automatically add a space.
            text += ' '
        self._cmd.setText(text)
        log.completion.debug("Placing cursor after '{}'".format(cursor_str))
        log.modes.debug("Completion triggered, focusing {!r}".format(self))
        self._cmd.setCursorPosition(len(cursor_str))
        self._cmd.setFocus()
        self._cmd.show_cmd.emit()

    @pyqtSlot()
    def on_text_edited(self):
        """Reset _empty_item_idx if text was edited."""
        self._empty_item_idx = None
Exemplo n.º 50
0
class BubblesWidget(BaseClass):
    """BubblesWidget(BaseClass)

    Provides a custom widget that shows a number of rising bubbles.
    Various properties are defined so that the user can customize the
    appearance of the widget, and change the number and behaviour of the
    bubbles shown.
    """

    # We define two signals that are used to indicate changes to the status
    # of the widget.
    bubbleLeft = pyqtSignal()
    bubblesRemaining = pyqtSignal(int)

    def __init__(self, parent=None):

        super(BubblesWidget, self).__init__(parent)

        self.pen = QPen(QColor("#cccccc"))
        self.bubbles = []
        self.backgroundColor1 = self.randomColor()
        self.backgroundColor2 = self.randomColor().darker(150)
        self.newBubble = None

        random.seed()

        self.animation_timer = QTimer(self)
        self.animation_timer.setSingleShot(False)
        self.animation_timer.timeout.connect(self.animate)
        self.animation_timer.start(25)

        self.bubbleTimer = QTimer()
        self.bubbleTimer.setSingleShot(False)
        self.bubbleTimer.timeout.connect(self.expandBubble)

        self.setMouseTracking(True)
        self.setMinimumSize(QSize(200, 200))
        self.setWindowTitle("Bubble Maker")

    def paintEvent(self, event):

        background = QRadialGradient(QPointF(self.rect().topLeft()), 500,
                                     QPointF(self.rect().bottomRight()))
        background.setColorAt(0, self.backgroundColor1)
        background.setColorAt(1, self.backgroundColor2)

        painter = QPainter()
        painter.begin(self)
        painter.setRenderHint(QPainter.Antialiasing)
        painter.fillRect(event.rect(), QBrush(background))

        painter.setPen(self.pen)

        for bubble in self.bubbles:

            if QRectF(
                    bubble.position - QPointF(bubble.radius, bubble.radius),
                    QSizeF(2 * bubble.radius, 2 * bubble.radius),
            ).intersects(QRectF(event.rect())):
                bubble.drawBubble(painter)

        if self.newBubble:

            self.newBubble.drawBubble(painter)

        painter.end()

    def mousePressEvent(self, event):

        if event.button() == Qt.LeftButton and self.newBubble is None:

            self.newBubble = Bubble(
                QPointF(event.pos()),
                4.0,
                1.0 + random.random() * 7,
                self.randomColor(),
                self.randomColor(),
            )
            self.bubbleTimer.start(50)
            event.accept()

    def mouseMoveEvent(self, event):

        if self.newBubble:

            self.update(
                QRectF(
                    self.newBubble.position - QPointF(
                        self.newBubble.radius + 1, self.newBubble.radius + 1),
                    QSizeF(2 * self.newBubble.radius + 2,
                           2 * self.newBubble.radius + 2),
                ).toRect())
            self.newBubble.position = QPointF(event.pos())
            self.update(
                QRectF(
                    self.newBubble.position - QPointF(
                        self.newBubble.radius + 1, self.newBubble.radius + 1),
                    QSizeF(2 * self.newBubble.radius + 2,
                           2 * self.newBubble.radius + 2),
                ).toRect())

        event.accept()

    def mouseReleaseEvent(self, event):

        if self.newBubble:

            self.bubbles.append(self.newBubble)
            self.newBubble = None
            self.bubbleTimer.stop()
            self.bubblesRemaining.emit(len(self.bubbles))

        event.accept()

    def expandBubble(self):

        if self.newBubble:

            self.newBubble.radius = min(self.newBubble.radius + 4.0,
                                        self.width() / 8.0,
                                        self.height() / 8.0)
            self.update(
                QRectF(
                    self.newBubble.position - QPointF(
                        self.newBubble.radius + 1, self.newBubble.radius + 1),
                    QSizeF(2 * self.newBubble.radius + 2,
                           2 * self.newBubble.radius + 2),
                ).toRect())
            self.newBubble.updateBrush()

    def randomColor(self):

        red = 205 + random.random() * 50
        green = 205 + random.random() * 50
        blue = 205 + random.random() * 50
        alpha = 91 + random.random() * 100

        return QColor(red, green, blue, alpha)

    def animate(self):

        bubbles = []
        left = False
        for bubble in self.bubbles:

            bubble.position = bubble.position + QPointF(0, -bubble.speed)

            self.update(
                QRectF(
                    bubble.position -
                    QPointF(bubble.radius + 1, bubble.radius + 1),
                    QSizeF(2 * bubble.radius + 2,
                           2 * bubble.radius + 2 + bubble.speed),
                ).toRect())

            if bubble.position.y() + bubble.radius > 0:
                bubbles.append(bubble)
            else:
                self.bubbleLeft.emit()
                left = True

        if self.newBubble:
            self.update(
                QRectF(
                    self.newBubble.position - QPointF(
                        self.newBubble.radius + 1, self.newBubble.radius + 1),
                    QSizeF(2 * self.newBubble.radius + 2,
                           2 * self.newBubble.radius + 2),
                ).toRect())

        self.bubbles = bubbles
        if left:
            self.bubblesRemaining.emit(len(self.bubbles))

    def sizeHint(self):

        return QSize(200, 200)

    # We provide getter and setter methods for the numberOfBubbles property.
    def getBubbles(self):

        return len(self.bubbles)

    # The setBubbles() method can also be used as a slot.
    @pyqtSlot(int)
    def setBubbles(self, value):

        value = max(0, value)

        while len(self.bubbles) < value:

            newBubble = Bubble(
                QPointF(random.random() * self.width(),
                        random.random() * self.height()),
                4.0 + random.random() * 20,
                1.0 + random.random() * 7,
                self.randomColor(),
                self.randomColor(),
            )
            newBubble.updateBrush()
            self.bubbles.append(newBubble)

        self.bubbles = self.bubbles[:value]
        self.bubblesRemaining.emit(value)
        self.update()

    numberOfBubbles = pyqtProperty(int, getBubbles, setBubbles)

    # We provide getter and setter methods for the color1 and color2
    # properties. The red, green and blue components for the QColor
    # values stored in these properties can be edited individually in
    # Qt Designer.

    def getColor1(self):

        return self.backgroundColor1

    def setColor1(self, value):

        self.backgroundColor1 = QColor(value)
        self.update()

    color1 = pyqtProperty(QColor, getColor1, setColor1)

    def getColor2(self):

        return self.backgroundColor2

    def setColor2(self, value):

        self.backgroundColor2 = QColor(value)
        self.update()

    color2 = pyqtProperty(QColor, getColor2, setColor2)

    # The stop() and start() slots provide simple control over the animation
    # of the bubbles in the widget.

    @pyqtSlot()
    def stop(self):

        self.animation_timer.stop()

    @pyqtSlot()
    def start(self):

        self.animation_timer.start(25)
Exemplo n.º 51
0
class ExtrudersModel(ListModel):
    """Model that holds extruders.

    This model is designed for use by any list of extruders, but specifically intended for drop-down lists of the
    current machine's extruders in place of settings.
    """

    # The ID of the container stack for the extruder.
    IdRole = Qt.UserRole + 1

    NameRole = Qt.UserRole + 2
    """Human-readable name of the extruder."""

    ColorRole = Qt.UserRole + 3
    """Colour of the material loaded in the extruder."""

    IndexRole = Qt.UserRole + 4
    """Index of the extruder, which is also the value of the setting itself.

    An index of 0 indicates the first extruder, an index of 1 the second one, and so on. This is the value that will 
    be saved in instance containers. """

    # The ID of the definition of the extruder.
    DefinitionRole = Qt.UserRole + 5

    # The material of the extruder.
    MaterialRole = Qt.UserRole + 6

    # The variant of the extruder.
    VariantRole = Qt.UserRole + 7
    StackRole = Qt.UserRole + 8

    MaterialBrandRole = Qt.UserRole + 9
    ColorNameRole = Qt.UserRole + 10

    EnabledRole = Qt.UserRole + 11
    """Is the extruder enabled?"""

    MaterialTypeRole = Qt.UserRole + 12
    """The type of the material (e.g. PLA, ABS, PETG, etc.)."""

    defaultColors = [
        "#ffc924", "#86ec21", "#22eeee", "#245bff", "#9124ff", "#ff24c8"
    ]
    """List of colours to display if there is no material or the material has no known colour. """
    def __init__(self, parent=None):
        """Initialises the extruders model, defining the roles and listening for changes in the data.

        :param parent: Parent QtObject of this list.
        """

        super().__init__(parent)

        self.addRoleName(self.IdRole, "id")
        self.addRoleName(self.NameRole, "name")
        self.addRoleName(self.EnabledRole, "enabled")
        self.addRoleName(self.ColorRole, "color")
        self.addRoleName(self.IndexRole, "index")
        self.addRoleName(self.DefinitionRole, "definition")
        self.addRoleName(self.MaterialRole, "material")
        self.addRoleName(self.VariantRole, "variant")
        self.addRoleName(self.StackRole, "stack")
        self.addRoleName(self.MaterialBrandRole, "material_brand")
        self.addRoleName(self.ColorNameRole, "color_name")
        self.addRoleName(self.MaterialTypeRole, "material_type")
        self._update_extruder_timer = QTimer()
        self._update_extruder_timer.setInterval(100)
        self._update_extruder_timer.setSingleShot(True)
        self._update_extruder_timer.timeout.connect(self.__updateExtruders)

        self._active_machine_extruders = []  # type: Iterable[ExtruderStack]
        self._add_optional_extruder = False

        # Listen to changes
        Application.getInstance().globalContainerStackChanged.connect(
            self._extrudersChanged
        )  # When the machine is swapped we must update the active machine extruders
        Application.getInstance(
        ).getExtruderManager().extrudersChanged.connect(
            self._extrudersChanged
        )  # When the extruders change we must link to the stack-changed signal of the new extruder
        Application.getInstance(
        ).getContainerRegistry().containerMetaDataChanged.connect(
            self._onExtruderStackContainersChanged
        )  # When meta data from a material container changes we must update
        self._extrudersChanged()  # Also calls _updateExtruders

    addOptionalExtruderChanged = pyqtSignal()

    def setAddOptionalExtruder(self, add_optional_extruder):
        if add_optional_extruder != self._add_optional_extruder:
            self._add_optional_extruder = add_optional_extruder
            self.addOptionalExtruderChanged.emit()
            self._updateExtruders()

    @pyqtProperty(bool,
                  fset=setAddOptionalExtruder,
                  notify=addOptionalExtruderChanged)
    def addOptionalExtruder(self):
        return self._add_optional_extruder

    def _extrudersChanged(self, machine_id=None):
        """Links to the stack-changed signal of the new extruders when an extruder is swapped out or added in the
         current machine.

        :param machine_id: The machine for which the extruders changed. This is filled by the
        ExtruderManager.extrudersChanged signal when coming from that signal. Application.globalContainerStackChanged
        doesn't fill this signal; it's assumed to be the current printer in that case.
        """

        machine_manager = Application.getInstance().getMachineManager()
        if machine_id is not None:
            if machine_manager.activeMachine is None:
                # No machine, don't need to update the current machine's extruders
                return
            if machine_id != machine_manager.activeMachine.getId():
                # Not the current machine
                return

        # Unlink from old extruders
        for extruder in self._active_machine_extruders:
            extruder.containersChanged.disconnect(
                self._onExtruderStackContainersChanged)
            extruder.enabledChanged.disconnect(self._updateExtruders)

        # Link to new extruders
        self._active_machine_extruders = []
        extruder_manager = Application.getInstance().getExtruderManager()
        for extruder in extruder_manager.getActiveExtruderStacks():
            if extruder is None:  #This extruder wasn't loaded yet. This happens asynchronously while this model is constructed from QML.
                continue
            extruder.containersChanged.connect(
                self._onExtruderStackContainersChanged)
            extruder.enabledChanged.connect(self._updateExtruders)
            self._active_machine_extruders.append(extruder)

        self._updateExtruders(
        )  # Since the new extruders may have different properties, update our own model.

    def _onExtruderStackContainersChanged(self, container):
        # Update when there is an empty container or material or variant change
        if container.getMetaDataEntry("type") in ["material", "variant", None]:
            # The ExtrudersModel needs to be updated when the material-name or -color changes, because the user identifies extruders by material-name
            self._updateExtruders()

    modelChanged = pyqtSignal()

    def _updateExtruders(self):
        self._update_extruder_timer.start()

    @UM.FlameProfiler.profile
    def __updateExtruders(self):
        """Update the list of extruders.

        This should be called whenever the list of extruders changes.
        """

        extruders_changed = False

        if self.count != 0:
            extruders_changed = True

        items = []

        global_container_stack = Application.getInstance(
        ).getGlobalContainerStack()
        if global_container_stack:

            # get machine extruder count for verification
            machine_extruder_count = global_container_stack.getProperty(
                "machine_extruder_count", "value")

            for extruder in Application.getInstance().getExtruderManager(
            ).getActiveExtruderStacks():
                position = extruder.getMetaDataEntry("position", default="0")
                try:
                    position = int(position)
                except ValueError:
                    # Not a proper int.
                    position = -1
                if position >= machine_extruder_count:
                    continue

                default_color = self.defaultColors[
                    position] if 0 <= position < len(
                        self.defaultColors) else self.defaultColors[0]
                color = extruder.material.getMetaDataEntry(
                    "color_code", default=default_color
                ) if extruder.material else default_color
                material_brand = extruder.material.getMetaDataEntry(
                    "brand", default="generic")
                color_name = extruder.material.getMetaDataEntry("color_name")
                # construct an item with only the relevant information
                item = {
                    "id":
                    extruder.getId(),
                    "name":
                    extruder.getName(),
                    "enabled":
                    extruder.isEnabled,
                    "color":
                    color,
                    "index":
                    position,
                    "definition":
                    extruder.getBottom().getId(),
                    "material":
                    extruder.material.getName() if extruder.material else "",
                    "variant":
                    extruder.variant.getName()
                    if extruder.variant else "",  # e.g. print core
                    "stack":
                    extruder,
                    "material_brand":
                    material_brand,
                    "color_name":
                    color_name,
                    "material_type":
                    extruder.material.getMetaDataEntry("material")
                    if extruder.material else "",
                }

                items.append(item)
                extruders_changed = True

        if extruders_changed:
            # sort by extruder index
            items.sort(key=lambda i: i["index"])

            # We need optional extruder to be last, so add it after we do sorting.
            # This way we can simply interpret the -1 of the index as the last item (which it now always is)
            if self._add_optional_extruder:
                item = {
                    "id": "",
                    "name": catalog.i18nc("@menuitem", "Not overridden"),
                    "enabled": True,
                    "color": "transparent",
                    "index": -1,
                    "definition": "",
                    "material": "",
                    "variant": "",
                    "stack": None,
                    "material_brand": "",
                    "color_name": "",
                    "material_type": "",
                }
                items.append(item)
            if self._items != items:
                self.setItems(items)
                self.modelChanged.emit()
Exemplo n.º 52
0
class CuraEngineBackend(QObject, Backend):
    backendError = Signal()

    ##  Starts the back-end plug-in.
    #
    #   This registers all the signal listeners and prepares for communication
    #   with the back-end in general.
    #   CuraEngineBackend is exposed to qml as well.
    def __init__(self) -> None:
        super().__init__()
        # Find out where the engine is located, and how it is called.
        # This depends on how Cura is packaged and which OS we are running on.
        executable_name = "CuraEngine"
        if Platform.isWindows():
            executable_name += ".exe"
        default_engine_location = executable_name
        if os.path.exists(
                os.path.join(CuraApplication.getInstallPrefix(), "bin",
                             executable_name)):
            default_engine_location = os.path.join(
                CuraApplication.getInstallPrefix(), "bin", executable_name)
        if hasattr(sys, "frozen"):
            default_engine_location = os.path.join(
                os.path.dirname(os.path.abspath(sys.executable)),
                executable_name)
        if Platform.isLinux() and not default_engine_location:
            if not os.getenv("PATH"):
                raise OSError(
                    "There is something wrong with your Linux installation.")
            for pathdir in cast(str, os.getenv("PATH")).split(os.pathsep):
                execpath = os.path.join(pathdir, executable_name)
                if os.path.exists(execpath):
                    default_engine_location = execpath
                    break

        self._application = CuraApplication.getInstance(
        )  #type: CuraApplication
        self._multi_build_plate_model = None  #type: Optional[MultiBuildPlateModel]
        self._machine_error_checker = None  #type: Optional[MachineErrorChecker]

        if not default_engine_location:
            raise EnvironmentError("Could not find CuraEngine")

        Logger.log("i", "Found CuraEngine at: %s", default_engine_location)

        default_engine_location = os.path.abspath(default_engine_location)
        self._application.getPreferences().addPreference(
            "backend/location", default_engine_location)

        # Workaround to disable layer view processing if layer view is not active.
        self._layer_view_active = False  #type: bool
        self._onActiveViewChanged()

        self._stored_layer_data = []  # type: List[Arcus.PythonMessage]
        self._stored_optimized_layer_data = {
        }  # type: Dict[int, List[Arcus.PythonMessage]] # key is build plate number, then arrays are stored until they go to the ProcessSlicesLayersJob

        self._scene = self._application.getController().getScene(
        )  #type: Scene
        self._scene.sceneChanged.connect(self._onSceneChanged)

        # Triggers for auto-slicing. Auto-slicing is triggered as follows:
        #  - auto-slicing is started with a timer
        #  - whenever there is a value change, we start the timer
        #  - sometimes an error check can get scheduled for a value change, in that case, we ONLY want to start the
        #    auto-slicing timer when that error check is finished
        # If there is an error check, stop the auto-slicing timer, and only wait for the error check to be finished
        # to start the auto-slicing timer again.
        #
        self._global_container_stack = None  #type: Optional[ContainerStack]

        # Listeners for receiving messages from the back-end.
        self._message_handlers["cura.proto.Layer"] = self._onLayerMessage
        self._message_handlers[
            "cura.proto.LayerOptimized"] = self._onOptimizedLayerMessage
        self._message_handlers["cura.proto.Progress"] = self._onProgressMessage
        self._message_handlers[
            "cura.proto.GCodeLayer"] = self._onGCodeLayerMessage
        self._message_handlers[
            "cura.proto.GCodePrefix"] = self._onGCodePrefixMessage
        self._message_handlers[
            "cura.proto.PrintTimeMaterialEstimates"] = self._onPrintTimeMaterialEstimates
        self._message_handlers[
            "cura.proto.SlicingFinished"] = self._onSlicingFinishedMessage

        self._start_slice_job = None  #type: Optional[StartSliceJob]
        self._start_slice_job_build_plate = None  #type: Optional[int]
        self._slicing = False  #type: bool # Are we currently slicing?
        self._restart = False  #type: bool # Back-end is currently restarting?
        self._tool_active = False  #type: bool # If a tool is active, some tasks do not have to do anything
        self._always_restart = True  #type: bool # Always restart the engine when starting a new slice. Don't keep the process running. TODO: Fix engine statelessness.
        self._process_layers_job = None  #type: Optional[ProcessSlicedLayersJob] # The currently active job to process layers, or None if it is not processing layers.
        self._build_plates_to_be_sliced = [
        ]  #type: List[int] # what needs slicing?
        self._engine_is_fresh = True  #type: bool # Is the newly started engine used before or not?

        self._backend_log_max_lines = 20000  #type: int # Maximum number of lines to buffer
        self._error_message = None  #type: Optional[Message] # Pop-up message that shows errors.
        self._last_num_objects = defaultdict(
            int
        )  #type: Dict[int, int] # Count number of objects to see if there is something changed
        self._postponed_scene_change_sources = [
        ]  #type: List[SceneNode] # scene change is postponed (by a tool)

        self._slice_start_time = None  #type: Optional[float]
        self._is_disabled = False  #type: bool

        self._application.getPreferences().addPreference(
            "general/auto_slice", False)

        self._use_timer = False  #type: bool
        # When you update a setting and other settings get changed through inheritance, many propertyChanged signals are fired.
        # This timer will group them up, and only slice for the last setting changed signal.
        # TODO: Properly group propertyChanged signals by whether they are triggered by the same user interaction.
        self._change_timer = QTimer()  #type: QTimer
        self._change_timer.setSingleShot(True)
        self._change_timer.setInterval(500)
        self.determineAutoSlicing()
        self._application.getPreferences().preferenceChanged.connect(
            self._onPreferencesChanged)

        self._application.initializationFinished.connect(self.initialize)

    def initialize(self) -> None:
        self._multi_build_plate_model = self._application.getMultiBuildPlateModel(
        )

        self._application.getController().activeViewChanged.connect(
            self._onActiveViewChanged)

        if self._multi_build_plate_model:
            self._multi_build_plate_model.activeBuildPlateChanged.connect(
                self._onActiveViewChanged)

        self._application.getMachineManager().globalContainerChanged.connect(
            self._onGlobalStackChanged)
        self._onGlobalStackChanged()

        # extruder enable / disable. Actually wanted to use machine manager here, but the initialization order causes it to crash
        ExtruderManager.getInstance().extrudersChanged.connect(
            self._extruderChanged)

        self.backendQuit.connect(self._onBackendQuit)
        self.backendConnected.connect(self._onBackendConnected)

        # When a tool operation is in progress, don't slice. So we need to listen for tool operations.
        self._application.getController().toolOperationStarted.connect(
            self._onToolOperationStarted)
        self._application.getController().toolOperationStopped.connect(
            self._onToolOperationStopped)

        self._machine_error_checker = self._application.getMachineErrorChecker(
        )
        self._machine_error_checker.errorCheckFinished.connect(
            self._onStackErrorCheckFinished)

    ##  Terminate the engine process.
    #
    #   This function should terminate the engine process.
    #   Called when closing the application.
    def close(self) -> None:
        # Terminate CuraEngine if it is still running at this point
        self._terminate()

    ##  Get the command that is used to call the engine.
    #   This is useful for debugging and used to actually start the engine.
    #   \return list of commands and args / parameters.
    def getEngineCommand(self) -> List[str]:
        command = [
            self._application.getPreferences().getValue("backend/location"),
            "connect", "127.0.0.1:{0}".format(self._port), ""
        ]

        parser = argparse.ArgumentParser(prog="cura", add_help=False)
        parser.add_argument(
            "--debug",
            action="store_true",
            default=False,
            help="Turn on the debug mode by setting this option.")
        known_args = vars(parser.parse_known_args()[0])
        if known_args["debug"]:
            command.append("-vvv")

        return command

    ##  Emitted when we get a message containing print duration and material amount.
    #   This also implies the slicing has finished.
    #   \param time The amount of time the print will take.
    #   \param material_amount The amount of material the print will use.
    printDurationMessage = Signal()

    ##  Emitted when the slicing process starts.
    slicingStarted = Signal()

    ##  Emitted when the slicing process is aborted forcefully.
    slicingCancelled = Signal()

    @pyqtSlot()
    def stopSlicing(self) -> None:
        self.setState(BackendState.NotStarted)
        if self._slicing:  # We were already slicing. Stop the old job.
            self._terminate()
            self._createSocket()

        if self._process_layers_job is not None:  # We were processing layers. Stop that, the layers are going to change soon.
            Logger.log("d", "Aborting process layers job...")
            self._process_layers_job.abort()
            self._process_layers_job = None

        if self._error_message:
            self._error_message.hide()

    ##  Manually triggers a reslice
    @pyqtSlot()
    def forceSlice(self) -> None:
        self.markSliceAll()
        self.slice()

    ##  Perform a slice of the scene.
    def slice(self) -> None:
        Logger.log("d", "Starting to slice...")
        self._slice_start_time = time()
        if not self._build_plates_to_be_sliced:
            self.processingProgress.emit(1.0)
            Logger.log(
                "w",
                "Slice unnecessary, nothing has changed that needs reslicing.")
            self.setState(BackendState.Done)
            return

        if self._process_layers_job:
            Logger.log("d", "Process layers job still busy, trying later.")
            return

        if not hasattr(self._scene, "gcode_dict"):
            self._scene.gcode_dict = {
            }  #type: ignore #Because we are creating the missing attribute here.

        # see if we really have to slice
        active_build_plate = self._application.getMultiBuildPlateModel(
        ).activeBuildPlate
        build_plate_to_be_sliced = self._build_plates_to_be_sliced.pop(0)
        Logger.log(
            "d", "Going to slice build plate [%s]!" % build_plate_to_be_sliced)
        num_objects = self._numObjectsPerBuildPlate()

        self._stored_layer_data = []

        if build_plate_to_be_sliced not in num_objects or num_objects[
                build_plate_to_be_sliced] == 0:
            self._scene.gcode_dict[build_plate_to_be_sliced] = [
            ]  #type: ignore #Because we created this attribute above.
            Logger.log("d",
                       "Build plate %s has no objects to be sliced, skipping",
                       build_plate_to_be_sliced)
            if self._build_plates_to_be_sliced:
                self.slice()
            return
        self._stored_optimized_layer_data[build_plate_to_be_sliced] = []
        if self._application.getPrintInformation(
        ) and build_plate_to_be_sliced == active_build_plate:
            self._application.getPrintInformation().setToZeroPrintInformation(
                build_plate_to_be_sliced)

        if self._process is None:  # type: ignore
            self._createSocket()
        self.stopSlicing()
        self._engine_is_fresh = False  # Yes we're going to use the engine

        self.processingProgress.emit(0.0)
        self.backendStateChange.emit(BackendState.NotStarted)

        self._scene.gcode_dict[build_plate_to_be_sliced] = [
        ]  #type: ignore #[] indexed by build plate number
        self._slicing = True
        self.slicingStarted.emit()

        self.determineAutoSlicing()  # Switch timer on or off if appropriate

        slice_message = self._socket.createMessage("cura.proto.Slice")
        self._start_slice_job = StartSliceJob(slice_message)
        self._start_slice_job_build_plate = build_plate_to_be_sliced
        self._start_slice_job.setBuildPlate(self._start_slice_job_build_plate)
        self._start_slice_job.start()
        self._start_slice_job.finished.connect(self._onStartSliceCompleted)

    ##  Terminate the engine process.
    #   Start the engine process by calling _createSocket()
    def _terminate(self) -> None:
        self._slicing = False
        self._stored_layer_data = []
        if self._start_slice_job_build_plate in self._stored_optimized_layer_data:
            del self._stored_optimized_layer_data[
                self._start_slice_job_build_plate]
        if self._start_slice_job is not None:
            self._start_slice_job.cancel()

        self.slicingCancelled.emit()
        self.processingProgress.emit(0)
        Logger.log("d", "Attempting to kill the engine process")

        if self._application.getUseExternalBackend():
            return

        if self._process is not None:  # type: ignore
            Logger.log("d", "Killing engine process")
            try:
                self._process.terminate()  # type: ignore
                Logger.log("d",
                           "Engine process is killed. Received return code %s",
                           self._process.wait())  # type: ignore
                self._process = None  # type: ignore

            except Exception as e:  # terminating a process that is already terminating causes an exception, silently ignore this.
                Logger.log(
                    "d",
                    "Exception occurred while trying to kill the engine %s",
                    str(e))

    ##  Event handler to call when the job to initiate the slicing process is
    #   completed.
    #
    #   When the start slice job is successfully completed, it will be happily
    #   slicing. This function handles any errors that may occur during the
    #   bootstrapping of a slice job.
    #
    #   \param job The start slice job that was just finished.
    def _onStartSliceCompleted(self, job: StartSliceJob) -> None:
        if self._error_message:
            self._error_message.hide()

        # Note that cancelled slice jobs can still call this method.
        if self._start_slice_job is job:
            self._start_slice_job = None

        if job.isCancelled() or job.getError() or job.getResult(
        ) == StartJobResult.Error:
            self.setState(BackendState.Error)
            self.backendError.emit(job)
            return

        if job.getResult() == StartJobResult.MaterialIncompatible:
            if self._application.platformActivity:
                self._error_message = Message(catalog.i18nc(
                    "@info:status",
                    "Unable to slice with the current material as it is incompatible with the selected machine or configuration."
                ),
                                              title=catalog.i18nc(
                                                  "@info:title",
                                                  "Unable to slice"))
                self._error_message.show()
                self.setState(BackendState.Error)
                self.backendError.emit(job)
            else:
                self.setState(BackendState.NotStarted)
            return

        if job.getResult() == StartJobResult.SettingError:
            if self._application.platformActivity:
                if not self._global_container_stack:
                    Logger.log(
                        "w",
                        "Global container stack not assigned to CuraEngineBackend!"
                    )
                    return
                extruders = ExtruderManager.getInstance(
                ).getActiveExtruderStacks()
                error_keys = []  #type: List[str]
                for extruder in extruders:
                    error_keys.extend(extruder.getErrorKeys())
                if not extruders:
                    error_keys = self._global_container_stack.getErrorKeys()
                error_labels = set()
                for key in error_keys:
                    for stack in [
                            self._global_container_stack
                    ] + extruders:  #Search all container stacks for the definition of this setting. Some are only in an extruder stack.
                        definitions = cast(
                            DefinitionContainerInterface,
                            stack.getBottom()).findDefinitions(key=key)
                        if definitions:
                            break  #Found it! No need to continue search.
                    else:  #No stack has a definition for this setting.
                        Logger.log(
                            "w",
                            "When checking settings for errors, unable to find definition for key: {key}"
                            .format(key=key))
                        continue
                    error_labels.add(definitions[0].label)

                self._error_message = Message(catalog.i18nc(
                    "@info:status",
                    "Unable to slice with the current settings. The following settings have errors: {0}"
                ).format(", ".join(error_labels)),
                                              title=catalog.i18nc(
                                                  "@info:title",
                                                  "Unable to slice"))
                self._error_message.show()
                self.setState(BackendState.Error)
                self.backendError.emit(job)
            else:
                self.setState(BackendState.NotStarted)
            return

        elif job.getResult() == StartJobResult.ObjectSettingError:
            errors = {}
            for node in DepthFirstIterator(
                    self._application.getController().getScene().getRoot()
            ):  #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
                stack = node.callDecoration("getStack")
                if not stack:
                    continue
                for key in stack.getErrorKeys():
                    if not self._global_container_stack:
                        Logger.log(
                            "e",
                            "CuraEngineBackend does not have global_container_stack assigned."
                        )
                        continue
                    definition = cast(DefinitionContainerInterface,
                                      self._global_container_stack.getBottom()
                                      ).findDefinitions(key=key)
                    if not definition:
                        Logger.log(
                            "e",
                            "When checking settings for errors, unable to find definition for key {key} in per-object stack."
                            .format(key=key))
                        continue
                    errors[key] = definition[0].label
            self._error_message = Message(catalog.i18nc(
                "@info:status",
                "Unable to slice due to some per-model settings. The following settings have errors on one or more models: {error_labels}"
            ).format(error_labels=", ".join(errors.values())),
                                          title=catalog.i18nc(
                                              "@info:title",
                                              "Unable to slice"))
            self._error_message.show()
            self.setState(BackendState.Error)
            self.backendError.emit(job)
            return

        if job.getResult() == StartJobResult.BuildPlateError:
            if self._application.platformActivity:
                self._error_message = Message(catalog.i18nc(
                    "@info:status",
                    "Unable to slice because the prime tower or prime position(s) are invalid."
                ),
                                              title=catalog.i18nc(
                                                  "@info:title",
                                                  "Unable to slice"))
                self._error_message.show()
                self.setState(BackendState.Error)
                self.backendError.emit(job)
            else:
                self.setState(BackendState.NotStarted)

        if job.getResult() == StartJobResult.ObjectsWithDisabledExtruder:
            self._error_message = Message(catalog.i18nc(
                "@info:status",
                "Unable to slice because there are objects associated with disabled Extruder %s."
                % job.getMessage()),
                                          title=catalog.i18nc(
                                              "@info:title",
                                              "Unable to slice"))
            self._error_message.show()
            self.setState(BackendState.Error)
            self.backendError.emit(job)
            return

        if job.getResult() == StartJobResult.NothingToSlice:
            if self._application.platformActivity:
                self._error_message = Message(catalog.i18nc(
                    "@info:status",
                    "Nothing to slice because none of the models fit the build volume or are assigned to a disabled extruder. Please scale or rotate models to fit, or enable an extruder."
                ),
                                              title=catalog.i18nc(
                                                  "@info:title",
                                                  "Unable to slice"))
                self._error_message.show()
                self.setState(BackendState.Error)
                self.backendError.emit(job)
            else:
                self.setState(BackendState.NotStarted)
            self._invokeSlice()
            return

        # Preparation completed, send it to the backend.
        self._socket.sendMessage(job.getSliceMessage())

        # Notify the user that it's now up to the backend to do it's job
        self.setState(BackendState.Processing)

        if self._slice_start_time:
            Logger.log("d", "Sending slice message took %s seconds",
                       time() - self._slice_start_time)

    ##  Determine enable or disable auto slicing. Return True for enable timer and False otherwise.
    #   It disables when
    #   - preference auto slice is off
    #   - decorator isBlockSlicing is found (used in g-code reader)
    def determineAutoSlicing(self) -> bool:
        enable_timer = True
        self._is_disabled = False

        if not self._application.getPreferences().getValue(
                "general/auto_slice"):
            enable_timer = False
        for node in DepthFirstIterator(
                self._scene.getRoot()
        ):  #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
            if node.callDecoration("isBlockSlicing"):
                enable_timer = False
                self.setState(BackendState.Disabled)
                self._is_disabled = True
            gcode_list = node.callDecoration("getGCodeList")
            if gcode_list is not None:
                self._scene.gcode_dict[node.callDecoration(
                    "getBuildPlateNumber"
                )] = gcode_list  #type: ignore #Because we generate this attribute dynamically.

        if self._use_timer == enable_timer:
            return self._use_timer
        if enable_timer:
            self.setState(BackendState.NotStarted)
            self.enableTimer()
            return True
        else:
            self.disableTimer()
            return False

    ##  Return a dict with number of objects per build plate
    def _numObjectsPerBuildPlate(self) -> Dict[int, int]:
        num_objects = defaultdict(int)  #type: Dict[int, int]
        for node in DepthFirstIterator(
                self._scene.getRoot()
        ):  #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
            # Only count sliceable objects
            if node.callDecoration("isSliceable"):
                build_plate_number = node.callDecoration("getBuildPlateNumber")
                if build_plate_number is not None:
                    num_objects[build_plate_number] += 1
        return num_objects

    ##  Listener for when the scene has changed.
    #
    #   This should start a slice if the scene is now ready to slice.
    #
    #   \param source The scene node that was changed.
    def _onSceneChanged(self, source: SceneNode) -> None:
        if not source.callDecoration("isSliceable"):
            return

        # This case checks if the source node is a node that contains GCode. In this case the
        # current layer data is removed so the previous data is not rendered - CURA-4821
        if source.callDecoration("isBlockSlicing") and source.callDecoration(
                "getLayerData"):
            self._stored_optimized_layer_data = {}

        build_plate_changed = set()
        source_build_plate_number = source.callDecoration(
            "getBuildPlateNumber")
        if source == self._scene.getRoot():
            # we got the root node
            num_objects = self._numObjectsPerBuildPlate()
            for build_plate_number in list(
                    self._last_num_objects.keys()) + list(num_objects.keys()):
                if build_plate_number not in self._last_num_objects or num_objects[
                        build_plate_number] != self._last_num_objects[
                            build_plate_number]:
                    self._last_num_objects[build_plate_number] = num_objects[
                        build_plate_number]
                    build_plate_changed.add(build_plate_number)
        else:
            # we got a single scenenode
            if not source.callDecoration("isGroup"):
                mesh_data = source.getMeshData()
                if mesh_data is None or mesh_data.getVertices() is None:
                    return

            # There are some SceneNodes that do not have any build plate associated, then do not add to the list.
            if source_build_plate_number is not None:
                build_plate_changed.add(source_build_plate_number)

        if not build_plate_changed:
            return

        if self._tool_active:
            # do it later, each source only has to be done once
            if source not in self._postponed_scene_change_sources:
                self._postponed_scene_change_sources.append(source)
            return

        self.stopSlicing()
        for build_plate_number in build_plate_changed:
            if build_plate_number not in self._build_plates_to_be_sliced:
                self._build_plates_to_be_sliced.append(build_plate_number)
            self.printDurationMessage.emit(source_build_plate_number, {}, [])
        self.processingProgress.emit(0.0)
        self._clearLayerData(build_plate_changed)

        self._invokeSlice()

    ##  Called when an error occurs in the socket connection towards the engine.
    #
    #   \param error The exception that occurred.
    def _onSocketError(self, error: Arcus.Error) -> None:
        if self._application.isShuttingDown():
            return

        super()._onSocketError(error)
        if error.getErrorCode() == Arcus.ErrorCode.Debug:
            return

        self._terminate()
        self._createSocket()

        if error.getErrorCode() not in [
                Arcus.ErrorCode.BindFailedError,
                Arcus.ErrorCode.ConnectionResetError, Arcus.ErrorCode.Debug
        ]:
            Logger.log("w", "A socket error caused the connection to be reset")

        # _terminate()' function sets the job status to 'cancel', after reconnecting to another Port the job status
        # needs to be updated. Otherwise backendState is "Unable To Slice"
        if error.getErrorCode(
        ) == Arcus.ErrorCode.BindFailedError and self._start_slice_job is not None:
            self._start_slice_job.setIsCancelled(False)

    ##  Remove old layer data (if any)
    def _clearLayerData(self, build_plate_numbers: Set = None) -> None:
        # Clear out any old gcode
        self._scene.gcode_dict = {}  # type: ignore

        for node in DepthFirstIterator(
                self._scene.getRoot()
        ):  #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
            if node.callDecoration("getLayerData"):
                if not build_plate_numbers or node.callDecoration(
                        "getBuildPlateNumber") in build_plate_numbers:
                    node.getParent().removeChild(node)

    def markSliceAll(self) -> None:
        for build_plate_number in range(
                self._application.getMultiBuildPlateModel().maxBuildPlate + 1):
            if build_plate_number not in self._build_plates_to_be_sliced:
                self._build_plates_to_be_sliced.append(build_plate_number)

    ##  Convenient function: mark everything to slice, emit state and clear layer data
    def needsSlicing(self) -> None:
        self.determineAutoSlicing()
        self.stopSlicing()
        self.markSliceAll()
        self.processingProgress.emit(0.0)
        if not self._use_timer:
            # With manually having to slice, we want to clear the old invalid layer data.
            self._clearLayerData()

    ##  A setting has changed, so check if we must reslice.
    # \param instance The setting instance that has changed.
    # \param property The property of the setting instance that has changed.
    def _onSettingChanged(self, instance: SettingInstance,
                          property: str) -> None:
        if property == "value":  # Only reslice if the value has changed.
            self.needsSlicing()
            self._onChanged()

        elif property == "validationState":
            if self._use_timer:
                self._change_timer.stop()

    def _onStackErrorCheckFinished(self) -> None:
        self.determineAutoSlicing()
        if self._is_disabled:
            return

        if not self._slicing and self._build_plates_to_be_sliced:
            self.needsSlicing()
            self._onChanged()

    ##  Called when a sliced layer data message is received from the engine.
    #
    #   \param message The protobuf message containing sliced layer data.
    def _onLayerMessage(self, message: Arcus.PythonMessage) -> None:
        self._stored_layer_data.append(message)

    ##  Called when an optimized sliced layer data message is received from the engine.
    #
    #   \param message The protobuf message containing sliced layer data.
    def _onOptimizedLayerMessage(self, message: Arcus.PythonMessage) -> None:
        if self._start_slice_job_build_plate is not None:
            if self._start_slice_job_build_plate not in self._stored_optimized_layer_data:
                self._stored_optimized_layer_data[
                    self._start_slice_job_build_plate] = []
            self._stored_optimized_layer_data[
                self._start_slice_job_build_plate].append(message)

    ##  Called when a progress message is received from the engine.
    #
    #   \param message The protobuf message containing the slicing progress.
    def _onProgressMessage(self, message: Arcus.PythonMessage) -> None:
        self.processingProgress.emit(message.amount)
        self.setState(BackendState.Processing)

    def _invokeSlice(self) -> None:
        if self._use_timer:
            # if the error check is scheduled, wait for the error check finish signal to trigger auto-slice,
            # otherwise business as usual
            if self._machine_error_checker is None:
                self._change_timer.stop()
                return

            if self._machine_error_checker.needToWaitForResult:
                self._change_timer.stop()
            else:
                self._change_timer.start()

    ##  Called when the engine sends a message that slicing is finished.
    #
    #   \param message The protobuf message signalling that slicing is finished.
    def _onSlicingFinishedMessage(self, message: Arcus.PythonMessage) -> None:
        self.setState(BackendState.Done)
        self.processingProgress.emit(1.0)

        gcode_list = self._scene.gcode_dict[
            self.
            _start_slice_job_build_plate]  #type: ignore #Because we generate this attribute dynamically.
        for index, line in enumerate(gcode_list):
            replaced = line.replace(
                "{print_time}",
                str(self._application.getPrintInformation().currentPrintTime.
                    getDisplayString(DurationFormat.Format.ISO8601)))
            replaced = replaced.replace(
                "{filament_amount}",
                str(self._application.getPrintInformation().materialLengths))
            replaced = replaced.replace(
                "{filament_weight}",
                str(self._application.getPrintInformation().materialWeights))
            replaced = replaced.replace(
                "{filament_cost}",
                str(self._application.getPrintInformation().materialCosts))
            replaced = replaced.replace(
                "{jobname}",
                str(self._application.getPrintInformation().jobName))

            gcode_list[index] = replaced

        self._slicing = False
        if self._slice_start_time:
            Logger.log("d", "Slicing took %s seconds",
                       time() - self._slice_start_time)
        Logger.log("d", "Number of models per buildplate: %s",
                   dict(self._numObjectsPerBuildPlate()))

        # See if we need to process the sliced layers job.
        active_build_plate = self._application.getMultiBuildPlateModel(
        ).activeBuildPlate
        if (self._layer_view_active
                and (self._process_layers_job is None
                     or not self._process_layers_job.isRunning())
                and active_build_plate == self._start_slice_job_build_plate
                and active_build_plate not in self._build_plates_to_be_sliced):

            self._startProcessSlicedLayersJob(active_build_plate)
        # self._onActiveViewChanged()
        self._start_slice_job_build_plate = None

        Logger.log("d", "See if there is more to slice...")
        # Somehow this results in an Arcus Error
        # self.slice()
        # Call slice again using the timer, allowing the backend to restart
        if self._build_plates_to_be_sliced:
            self.enableTimer(
            )  # manually enable timer to be able to invoke slice, also when in manual slice mode
            self._invokeSlice()

    ##  Called when a g-code message is received from the engine.
    #
    #   \param message The protobuf message containing g-code, encoded as UTF-8.
    def _onGCodeLayerMessage(self, message: Arcus.PythonMessage) -> None:
        self._scene.gcode_dict[self._start_slice_job_build_plate].append(
            message.data.decode("utf-8", "replace")
        )  #type: ignore #Because we generate this attribute dynamically.

    ##  Called when a g-code prefix message is received from the engine.
    #
    #   \param message The protobuf message containing the g-code prefix,
    #   encoded as UTF-8.
    def _onGCodePrefixMessage(self, message: Arcus.PythonMessage) -> None:
        self._scene.gcode_dict[self._start_slice_job_build_plate].insert(
            0, message.data.decode("utf-8", "replace")
        )  #type: ignore #Because we generate this attribute dynamically.

    ##  Creates a new socket connection.
    def _createSocket(self, protocol_file: str = None) -> None:
        if not protocol_file:
            plugin_path = PluginRegistry.getInstance().getPluginPath(
                self.getPluginId())
            if not plugin_path:
                Logger.log("e", "Could not get plugin path!",
                           self.getPluginId())
                return
            protocol_file = os.path.abspath(
                os.path.join(plugin_path, "Cura.proto"))
        super()._createSocket(protocol_file)
        self._engine_is_fresh = True

    ##  Called when anything has changed to the stuff that needs to be sliced.
    #
    #   This indicates that we should probably re-slice soon.
    def _onChanged(self, *args: Any, **kwargs: Any) -> None:
        self.needsSlicing()
        if self._use_timer:
            # if the error check is scheduled, wait for the error check finish signal to trigger auto-slice,
            # otherwise business as usual
            if self._machine_error_checker is None:
                self._change_timer.stop()
                return

            if self._machine_error_checker.needToWaitForResult:
                self._change_timer.stop()
            else:
                self._change_timer.start()

    ##  Called when a print time message is received from the engine.
    #
    #   \param message The protobuf message containing the print time per feature and
    #   material amount per extruder
    def _onPrintTimeMaterialEstimates(self,
                                      message: Arcus.PythonMessage) -> None:
        material_amounts = []
        for index in range(message.repeatedMessageCount("materialEstimates")):
            material_amounts.append(
                message.getRepeatedMessage("materialEstimates",
                                           index).material_amount)

        times = self._parseMessagePrintTimes(message)
        self.printDurationMessage.emit(self._start_slice_job_build_plate,
                                       times, material_amounts)

    ##  Called for parsing message to retrieve estimated time per feature
    #
    #   \param message The protobuf message containing the print time per feature
    def _parseMessagePrintTimes(
            self, message: Arcus.PythonMessage) -> Dict[str, float]:
        result = {
            "inset_0": message.time_inset_0,
            "inset_x": message.time_inset_x,
            "skin": message.time_skin,
            "infill": message.time_infill,
            "support_infill": message.time_support_infill,
            "support_interface": message.time_support_interface,
            "support": message.time_support,
            "skirt": message.time_skirt,
            "prime_tower": message.time_prime_tower,
            "travel": message.time_travel,
            "retract": message.time_retract,
            "none": message.time_none
        }
        return result

    ##  Called when the back-end connects to the front-end.
    def _onBackendConnected(self) -> None:
        if self._restart:
            self._restart = False
            self._onChanged()

    ##  Called when the user starts using some tool.
    #
    #   When the user starts using a tool, we should pause slicing to prevent
    #   continuously slicing while the user is dragging some tool handle.
    #
    #   \param tool The tool that the user is using.
    def _onToolOperationStarted(self, tool: Tool) -> None:
        self._tool_active = True  # Do not react on scene change
        self.disableTimer()
        # Restart engine as soon as possible, we know we want to slice afterwards
        if not self._engine_is_fresh:
            self._terminate()
            self._createSocket()

    ##  Called when the user stops using some tool.
    #
    #   This indicates that we can safely start slicing again.
    #
    #   \param tool The tool that the user was using.
    def _onToolOperationStopped(self, tool: Tool) -> None:
        self._tool_active = False  # React on scene change again
        self.determineAutoSlicing()  # Switch timer on if appropriate
        # Process all the postponed scene changes
        while self._postponed_scene_change_sources:
            source = self._postponed_scene_change_sources.pop(0)
            self._onSceneChanged(source)

    def _startProcessSlicedLayersJob(self, build_plate_number: int) -> None:
        self._process_layers_job = ProcessSlicedLayersJob(
            self._stored_optimized_layer_data[build_plate_number])
        self._process_layers_job.setBuildPlate(build_plate_number)
        self._process_layers_job.finished.connect(
            self._onProcessLayersFinished)
        self._process_layers_job.start()

    ##  Called when the user changes the active view mode.
    def _onActiveViewChanged(self) -> None:
        view = self._application.getController().getActiveView()
        if view:
            active_build_plate = self._application.getMultiBuildPlateModel(
            ).activeBuildPlate
            if view.getPluginId(
            ) == "SimulationView":  # If switching to layer view, we should process the layers if that hasn't been done yet.
                self._layer_view_active = True
                # There is data and we're not slicing at the moment
                # if we are slicing, there is no need to re-calculate the data as it will be invalid in a moment.
                # TODO: what build plate I am slicing
                if (active_build_plate in self._stored_optimized_layer_data
                        and not self._slicing and not self._process_layers_job
                        and active_build_plate
                        not in self._build_plates_to_be_sliced):

                    self._startProcessSlicedLayersJob(active_build_plate)
            else:
                self._layer_view_active = False

    ##  Called when the back-end self-terminates.
    #
    #   We should reset our state and start listening for new connections.
    def _onBackendQuit(self) -> None:
        if not self._restart:
            if self._process:  # type: ignore
                Logger.log(
                    "d",
                    "Backend quit with return code %s. Resetting process and socket.",
                    self._process.wait())  # type: ignore
                self._process = None  # type: ignore

    ##  Called when the global container stack changes
    def _onGlobalStackChanged(self) -> None:
        if self._global_container_stack:
            self._global_container_stack.propertyChanged.disconnect(
                self._onSettingChanged)
            self._global_container_stack.containersChanged.disconnect(
                self._onChanged)
            extruders = list(self._global_container_stack.extruders.values())

            for extruder in extruders:
                extruder.propertyChanged.disconnect(self._onSettingChanged)
                extruder.containersChanged.disconnect(self._onChanged)

        self._global_container_stack = self._application.getMachineManager(
        ).activeMachine

        if self._global_container_stack:
            self._global_container_stack.propertyChanged.connect(
                self._onSettingChanged
            )  # Note: Only starts slicing when the value changed.
            self._global_container_stack.containersChanged.connect(
                self._onChanged)
            extruders = list(self._global_container_stack.extruders.values())
            for extruder in extruders:
                extruder.propertyChanged.connect(self._onSettingChanged)
                extruder.containersChanged.connect(self._onChanged)
            self._onChanged()

    def _onProcessLayersFinished(self, job: ProcessSlicedLayersJob) -> None:
        if job.getBuildPlate() in self._stored_optimized_layer_data:
            del self._stored_optimized_layer_data[job.getBuildPlate()]
        else:
            Logger.log(
                "w",
                "The optimized layer data was already deleted for buildplate %s",
                job.getBuildPlate())
        self._process_layers_job = None
        Logger.log("d", "See if there is more to slice(2)...")
        self._invokeSlice()

    ##  Connect slice function to timer.
    def enableTimer(self) -> None:
        if not self._use_timer:
            self._change_timer.timeout.connect(self.slice)
            self._use_timer = True

    ##  Disconnect slice function from timer.
    #   This means that slicing will not be triggered automatically
    def disableTimer(self) -> None:
        if self._use_timer:
            self._use_timer = False
            self._change_timer.timeout.disconnect(self.slice)

    def _onPreferencesChanged(self, preference: str) -> None:
        if preference != "general/auto_slice":
            return
        auto_slice = self.determineAutoSlicing()
        if auto_slice:
            self._change_timer.start()

    ##   Tickle the backend so in case of auto slicing, it starts the timer.
    def tickle(self) -> None:
        if self._use_timer:
            self._change_timer.start()

    def _extruderChanged(self) -> None:
        if not self._multi_build_plate_model:
            Logger.log(
                "w",
                "CuraEngineBackend does not have multi_build_plate_model assigned!"
            )
            return
        for build_plate_number in range(
                self._multi_build_plate_model.maxBuildPlate + 1):
            if build_plate_number not in self._build_plates_to_be_sliced:
                self._build_plates_to_be_sliced.append(build_plate_number)
        self._invokeSlice()
class DiscoverRepetierAction(MachineAction):
    def __init__(self, parent: QObject = None) -> None:
        super().__init__("DiscoverRepetierAction",
                         catalog.i18nc("@action", "Connect Repetier"))

        self._qml_url = "DiscoverRepetierAction.qml"

        self._application = CuraApplication.getInstance()
        self._network_plugin = None

        #   QNetwork manager needs to be created in advance. If we don't it can happen that it doesn't correctly
        #   hook itself into the event loop, which results in events never being fired / done.
        self._network_manager = QNetworkAccessManager()
        self._network_manager.finished.connect(self._onRequestFinished)
        self._printers = [""]
        self._printerlist_reply = None
        self._settings_reply = None
        self._settings_reply_timeout = None  # type: Optional[NetworkReplyTimeout]

        self._instance_supports_appkeys = False
        self._appkey_reply = None  # type: Optional[QNetworkReply]
        self._appkey_request = None  # type: Optional[QNetworkRequest]
        self._appkey_instance_id = ""

        self._appkey_poll_timer = QTimer()
        self._appkey_poll_timer.setInterval(500)
        self._appkey_poll_timer.setSingleShot(True)
        self._appkey_poll_timer.timeout.connect(self._pollApiKey)

        # Try to get version information from plugin.json
        plugin_file_path = os.path.join(
            os.path.dirname(os.path.abspath(__file__)), "plugin.json")
        try:
            with open(plugin_file_path) as plugin_file:
                plugin_info = json.load(plugin_file)
                self._plugin_version = plugin_info["version"]
        except:
            # The actual version info is not critical to have so we can continue
            self._plugin_version = "0.0"
            Logger.logException(
                "w", "Could not get version information for the plugin")

        self._user_agent = (
            "%s/%s %s/%s" %
            (self._application.getApplicationName(),
             self._application.getVersion(), "RepetierIntegration",
             self._plugin_version)).encode()

        self._settings_instance = None

        self._instance_responded = False
        self._instance_in_error = False
        self._instance_api_key_accepted = False
        self._instance_supports_sd = False
        self._instance_supports_camera = False
        self._instance_webcamflip_y = False
        self._instance_webcamflip_x = False
        self._instance_webcamrot90 = False
        self._instance_webcamrot270 = False

        # Load keys cache from preferences
        self._preferences = self._application.getPreferences()
        self._preferences.addPreference("Repetier/keys_cache", "")

        try:
            self._keys_cache = json.loads(
                self._preferences.getValue("Repetier/keys_cache"))
        except ValueError:
            self._keys_cache = {}
        if not isinstance(self._keys_cache, dict):
            self._keys_cache = {}

        self._additional_components = None

        ContainerRegistry.getInstance().containerAdded.connect(
            self._onContainerAdded)
        self._application.engineCreatedSignal.connect(
            self._createAdditionalComponentsView)

    @pyqtProperty(str, constant=True)
    def pluginVersion(self) -> str:
        return self._plugin_version

    @pyqtSlot()
    def startDiscovery(self) -> None:
        if not self._plugin_id:
            return
        if not self._network_plugin:
            self._network_plugin = cast(
                RepetierOutputDevicePlugin,
                self._application.getOutputDeviceManager().
                getOutputDevicePlugin(self._plugin_id))
            if not self._network_plugin:
                return
            self._network_plugin.addInstanceSignal.connect(
                self._onInstanceDiscovery)
            self._network_plugin.removeInstanceSignal.connect(
                self._onInstanceDiscovery)
            self._network_plugin.instanceListChanged.connect(
                self._onInstanceDiscovery)
            self.instancesChanged.emit()
        else:
            # Restart bonjour discovery
            self._network_plugin.startDiscovery()

    def _onInstanceDiscovery(self, *args) -> None:
        self.instancesChanged.emit()

    @pyqtSlot(str)
    def removeManualInstance(self, name: str) -> None:
        if not self._network_plugin:
            return

        self._network_plugin.removeManualInstance(name)

    @pyqtSlot(str, str, int, str, bool, str, str, str)
    def setManualInstance(self, name, address, port, path, useHttps, userName,
                          password, repetierid):
        if not self._network_plugin:
            return
        # This manual printer could replace a current manual printer
        self._network_plugin.removeManualInstance(name)

        self._network_plugin.addManualInstance(name, address, port, path,
                                               useHttps, userName, password,
                                               repetierid)

    def _onContainerAdded(self, container: "ContainerInterface") -> None:
        # Add this action as a supported action to all machine definitions
        if (isinstance(container, DefinitionContainer)
                and container.getMetaDataEntry("type") == "machine"
                and container.getMetaDataEntry("supports_usb_connection")):

            self._application.getMachineActionManager().addSupportedAction(
                container.getId(), self.getKey())

    instancesChanged = pyqtSignal()
    appKeysSupportedChanged = pyqtSignal()
    appKeyReceived = pyqtSignal()
    instanceIdChanged = pyqtSignal()

    @pyqtProperty("QVariantList", notify=instancesChanged)
    def discoveredInstances(self) -> List[Any]:
        if self._network_plugin:
            instances = list(self._network_plugin.getInstances().values())
            instances.sort(key=lambda k: k.name)
            return instances
        else:
            return []

    @pyqtSlot(str)
    def setInstanceId(self, key: str) -> None:
        global_container_stack = self._application.getGlobalContainerStack()
        if global_container_stack:
            global_container_stack.setMetaDataEntry("repetier_id", key)

        if self._network_plugin:
            # Ensure that the connection states are refreshed.
            self._network_plugin.reCheckConnections()

        self.instanceIdChanged.emit()

    @pyqtProperty(str, notify=instanceIdChanged)
    def instanceId(self) -> str:
        global_container_stack = self._application.getGlobalContainerStack()
        if not global_container_stack:
            return ""

        return global_container_stack.getMetaDataEntry("repetier_id", "")

    @pyqtSlot(str)
    @pyqtSlot(result=str)
    def getInstanceId(self) -> str:
        global_container_stack = self._application.getGlobalContainerStack()
        if not global_container_stack:
            Logger.log(
                "d",
                "getInstancdId - self._application.getGlobalContainerStack() returned nothing"
            )
            return ""

        return global_container_stack.getMetaDataEntry("repetier_id", "")

    @pyqtSlot(str)
    def requestApiKey(self, instance_id: str) -> None:
        (instance, base_url, basic_auth_username,
         basic_auth_password) = self._getInstanceInfo(instance_id)

        if not base_url:

            return

        ## Request appkey
        self._appkey_instance_id = instance_id
        self._appkey_request = self._createRequest(
            QUrl(base_url + "plugin/appkeys/request"), basic_auth_username,
            basic_auth_password)
        self._appkey_request.setRawHeader(b"Content-Type", b"application/json")
        data = json.dumps({"app": "Cura"})
        self._appkey_reply = self._network_manager.post(
            self._appkey_request, data.encode())

    @pyqtSlot()
    def cancelApiKeyRequest(self) -> None:
        if self._appkey_reply:
            if self._appkey_reply.isRunning():
                self._appkey_reply.abort()
            self._appkey_reply = None

        self._appkey_request = None  # type: Optional[QNetworkRequest]

        self._appkey_poll_timer.stop()

    def _pollApiKey(self) -> None:
        if not self._appkey_request:
            return
        self._appkey_reply = self._network_manager.get(self._appkey_request)

    @pyqtSlot(str)
    def probeAppKeySupport(self, instance_id: str) -> None:
        (instance, base_url, basic_auth_username,
         basic_auth_password) = self._getInstanceInfo(instance_id)
        if not base_url or not instance:
            return

        instance.getAdditionalData()

        self._instance_supports_appkeys = False
        self.appKeysSupportedChanged.emit()

        appkey_probe_request = self._createRequest(
            QUrl(base_url + "plugin/appkeys/probe"), basic_auth_username,
            basic_auth_password)
        self._appkey_reply = self._network_manager.get(appkey_probe_request)

    @pyqtSlot(str)
    def getPrinterList(self, base_url):
        self._instance_responded = False
        url = QUrl("http://" + base_url + "/printer/info")
        Logger.log("d", "getPrinterList:" + url.toString())
        settings_request = QNetworkRequest(url)
        settings_request.setRawHeader("User-Agent".encode(), self._user_agent)
        self._printerlist_reply = self._network_manager.get(settings_request)
        return self._printers

    @pyqtSlot(str, str, str, str, str, str)
    def testApiKey(self,
                   instance_id: str,
                   base_url,
                   api_key,
                   basic_auth_username="",
                   basic_auth_password="",
                   work_id="") -> None:
        (instance, base_url, basic_auth_username,
         basic_auth_password) = self._getInstanceInfo(instance_id)
        self._instance_responded = False
        self._instance_api_key_accepted = False
        self._instance_supports_sd = False
        self._instance_webcamflip_y = False
        self._instance_webcamflip_x = False
        self._instance_webcamrot90 = False
        self._instance_webcamrot270 = False
        self._instance_supports_camera = False
        self.selectedInstanceSettingsChanged.emit()
        if self._settings_reply:
            if self._settings_reply.isRunning():
                self._settings_reply.abort()
            self._settings_reply = None
        if self._settings_reply_timeout:
            self._settings_reply_timeout = None
        if ((api_key != "") and (api_key != None) and (work_id != "")):
            Logger.log(
                "d",
                "Trying to access Repetier instance at %s with the provided API key."
                % base_url)
            Logger.log("d", "Using %s as work_id" % work_id)
            Logger.log("d", "Using %s as api_key" % api_key)
            url = QUrl(base_url + "/printer/api/" + work_id +
                       "?a=getPrinterConfig&apikey=" + api_key)
            settings_request = QNetworkRequest(url)
            settings_request.setRawHeader("x-api-key".encode(),
                                          api_key.encode())
            settings_request.setRawHeader("User-Agent".encode(),
                                          self._user_agent)
            if basic_auth_username and basic_auth_password:
                data = base64.b64encode(
                    ("%s:%s" % (basic_auth_username,
                                basic_auth_password)).encode()).decode("utf-8")
                settings_request.setRawHeader("Authorization".encode(),
                                              ("Basic %s" % data).encode())
            self._settings_reply = self._network_manager.get(settings_request)
            self._settings_instance = instance
        else:
            self.getPrinterList(base_url)

    @pyqtSlot(str)
    def setApiKey(self, api_key: str) -> None:
        global_container_stack = self._application.getGlobalContainerStack()
        if not global_container_stack:
            return
        global_container_stack.setMetaDataEntry("repetier_api_key", api_key)
        self._keys_cache[self.getInstanceId()] = api_key
        keys_cache = base64.b64encode(
            json.dumps(self._keys_cache).encode("ascii")).decode("ascii")
        self._preferences.setValue("Repetier/keys_cache", keys_cache)

        if self._network_plugin:
            # Ensure that the connection states are refreshed.
            self._network_plugin.reCheckConnections()

    #  Get the stored API key of this machine
    #   \return key String containing the key of the machine.
    @pyqtSlot(str, result=str)
    def getApiKey(self, instance_id: str) -> str:
        global_container_stack = self._application.getGlobalContainerStack()
        if not global_container_stack:
            return ""
        Logger.log(
            "d", "APIKEY read %s" %
            global_container_stack.getMetaDataEntry("repetier_api_key", ""))
        if instance_id == self.getInstanceId():
            api_key = global_container_stack.getMetaDataEntry(
                "repetier_api_key", "")
        else:
            api_key = self._keys_cache.get(instance_id, "")
        return api_key

    selectedInstanceSettingsChanged = pyqtSignal()

    @pyqtProperty(list)
    def getPrinters(self):
        return self._printers

    @pyqtProperty(bool, notify=selectedInstanceSettingsChanged)
    def instanceResponded(self) -> bool:
        return self._instance_responded

    @pyqtProperty(bool, notify=selectedInstanceSettingsChanged)
    def instanceInError(self) -> bool:
        return self._instance_in_error

    @pyqtProperty(bool, notify=selectedInstanceSettingsChanged)
    def instanceApiKeyAccepted(self) -> bool:
        return self._instance_api_key_accepted

    @pyqtProperty(bool, notify=selectedInstanceSettingsChanged)
    def instanceSupportsSd(self) -> bool:
        return self._instance_supports_sd

    @pyqtProperty(bool, notify=selectedInstanceSettingsChanged)
    def instanceWebcamFlipY(self):
        return self._instance_webcamflip_y

    @pyqtProperty(bool, notify=selectedInstanceSettingsChanged)
    def instanceWebcamFlipX(self):
        return self._instance_webcamflip_x

    @pyqtProperty(bool, notify=selectedInstanceSettingsChanged)
    def instanceWebcamRot90(self):
        return self._instance_webcamrot90

    @pyqtProperty(bool, notify=selectedInstanceSettingsChanged)
    def instanceWebcamRot270(self):
        return self._instance_webcamrot270

    @pyqtProperty(bool, notify=selectedInstanceSettingsChanged)
    def instanceSupportsCamera(self) -> bool:
        return self._instance_supports_camera

    @pyqtSlot(str, str, str)
    def setContainerMetaDataEntry(self, container_id: str, key: str,
                                  value: str) -> None:
        containers = ContainerRegistry.getInstance().findContainers(
            id=container_id)
        if not containers:
            Logger.log(
                "w",
                "Could not set metadata of container %s because it was not found.",
                container_id)
            return

        containers[0].setMetaDataEntry(key, value)

    @pyqtSlot(bool)
    def applyGcodeFlavorFix(self, apply_fix: bool) -> None:
        global_container_stack = self._application.getGlobalContainerStack()
        if not global_container_stack:
            return

        gcode_flavor = "RepRap (Marlin/Sprinter)" if apply_fix else "UltiGCode"
        if global_container_stack.getProperty("machine_gcode_flavor",
                                              "value") == gcode_flavor:
            # No need to add a definition_changes container if the setting is not going to be changed
            return

        # Make sure there is a definition_changes container to store the machine settings
        definition_changes_container = global_container_stack.definitionChanges
        if definition_changes_container == ContainerRegistry.getInstance(
        ).getEmptyInstanceContainer():
            definition_changes_container = CuraStackBuilder.createDefinitionChangesContainer(
                global_container_stack,
                global_container_stack.getId() + "_settings")

        definition_changes_container.setProperty("machine_gcode_flavor",
                                                 "value", gcode_flavor)

        # Update the has_materials metadata flag after switching gcode flavor
        definition = global_container_stack.getBottom()
        if (not definition or definition.getProperty("machine_gcode_flavor",
                                                     "value") != "UltiGCode"
                or definition.getMetaDataEntry("has_materials", False)):

            # In other words: only continue for the UM2 (extended), but not for the UM2+
            return

        has_materials = global_container_stack.getProperty(
            "machine_gcode_flavor", "value") != "UltiGCode"

        material_container = global_container_stack.material

        if has_materials:
            global_container_stack.setMetaDataEntry("has_materials", True)

            # Set the material container to a sane default
            if material_container == ContainerRegistry.getInstance(
            ).getEmptyInstanceContainer():
                search_criteria = {
                    "type":
                    "material",
                    "definition":
                    "fdmprinter",
                    "id":
                    global_container_stack.getMetaDataEntry(
                        "preferred_material")
                }
                materials = ContainerRegistry.getInstance(
                ).findInstanceContainers(**search_criteria)
                if materials:
                    global_container_stack.material = materials[0]
        else:
            # The metadata entry is stored in an ini, and ini files are parsed as strings only.
            # Because any non-empty string evaluates to a boolean True, we have to remove the entry to make it False.
            if "has_materials" in global_container_stack.getMetaData():
                global_container_stack.removeMetaDataEntry("has_materials")

            global_container_stack.material = ContainerRegistry.getInstance(
            ).getEmptyInstanceContainer()

        self._application.globalContainerStackChanged.emit()

    @pyqtSlot(str)
    def openWebPage(self, url: str) -> None:
        QDesktopServices.openUrl(QUrl(url))

    def _createAdditionalComponentsView(self) -> None:
        Logger.log(
            "d",
            "Creating additional ui components for Repetier-connected printers."
        )

        path = os.path.join(os.path.dirname(os.path.abspath(__file__)),
                            "RepetierComponents.qml")
        self._additional_components = self._application.createQmlComponent(
            path, {"manager": self})
        if not self._additional_components:
            Logger.log(
                "w",
                "Could not create additional components for Repetier-connected printers."
            )
            return

        self._application.addAdditionalComponent(
            "monitorButtons",
            self._additional_components.findChild(QObject,
                                                  "openRepetierButton"))

    def _onRequestFailed(self, reply: QNetworkReply) -> None:
        if reply.operation() == QNetworkAccessManager.GetOperation:
            if "api/settings" in reply.url().toString(
            ):  # Repetier settings dump from /settings:
                Logger.log(
                    "w",
                    "Connection refused or timeout when trying to access Repetier at %s"
                    % reply.url().toString())
                self._instance_in_error = True
                self.selectedInstanceSettingsChanged.emit()

    #  Handler for all requests that have finished.
    def _onRequestFinished(self, reply: QNetworkReply) -> None:
        if reply.error() == QNetworkReply.TimeoutError:
            QMessageBox.warning(None, 'Connection Timeout',
                                'Connection Timeout')
            return
        http_status_code = reply.attribute(
            QNetworkRequest.HttpStatusCodeAttribute)
        if not http_status_code:
            #QMessageBox.warning(None,'Connection Attempt2',http_status_code)
            # Received no or empty reply
            Logger.log("d", "Received no or empty reply")
            return

        if reply.operation() == QNetworkAccessManager.GetOperation:
            Logger.log("d", reply.url().toString())
            if "printer/info" in reply.url().toString(
            ):  # Repetier settings dump from printer/info:
                if http_status_code == 200:
                    try:
                        json_data = json.loads(
                            bytes(reply.readAll()).decode("utf-8"))
                        Logger.log("d", reply.url().toString())
                        Logger.log("d", json_data)
                    except json.decoder.JSONDecodeError:
                        Logger.log(
                            "w",
                            "Received invalid JSON from Repetier instance.")
                        json_data = {}

                    if "printers" in json_data:
                        Logger.log("d", "DiscoverRepetierAction: printers: %s",
                                   len(json_data["printers"]))
                        if len(json_data["printers"]) > 0:
                            self._printers = [""]
                            for printerinfo in json_data["printers"]:
                                Logger.log("d", "Slug: %s",
                                           printerinfo["slug"])
                                self._printers.append(printerinfo["slug"])

                    if "apikey" in json_data:
                        Logger.log("d", "DiscoverRepetierAction: apikey: %s",
                                   json_data["apikey"])
                        global_container_stack = self._application.getGlobalContainerStack(
                        )
                        if not global_container_stack:
                            return
                        global_container_stack.setMetaDataEntry(
                            "repetier_api_key", json_data["apikey"])
                        self._keys_cache[
                            self.getInstanceId()] = json_data["apikey"]
                        keys_cache = base64.b64encode(
                            json.dumps(self._keys_cache).encode(
                                "ascii")).decode("ascii")
                        self._preferences.setValue("Repetier/keys_cache",
                                                   keys_cache)
                        self.appKeyReceived.emit()

        if self._network_plugin:
            # Ensure that the connection states are refreshed.
            self._network_plugin.reCheckConnections()

            if "getPrinterConfig" in reply.url().toString(
            ):  # Repetier settings dump from getPrinterConfig:
                if http_status_code == 200:
                    Logger.log("d", "API key accepted by Repetier.")
                    self._instance_api_key_accepted = True

                    try:
                        json_data = json.loads(
                            bytes(reply.readAll()).decode("utf-8"))
                        Logger.log("d", reply.url().toString())
                        Logger.log("d", json_data)
                    except json.decoder.JSONDecodeError:
                        Logger.log(
                            "w",
                            "Received invalid JSON from Repetier instance.")
                        json_data = {}

                    if "general" in json_data and "sdcard" in json_data[
                            "general"]:
                        self._instance_supports_sd = json_data["general"][
                            "sdcard"]

                    if "webcam" in json_data and "dynamicUrl" in json_data[
                            "webcam"]:
                        Logger.log(
                            "d", "DiscoverRepetierAction: Checking streamurl")
                        Logger.log("d", "DiscoverRepetierAction: %s",
                                   reply.url())
                        stream_url = json_data["webcam"]["dynamicUrl"].replace(
                            "127.0.0.1",
                            re.findall(r'[0-9]+(?:\.[0-9]+){3}',
                                       reply.url().toString())[0])
                        Logger.log("d",
                                   "DiscoverRepetierAction: stream_url: %s",
                                   stream_url)
                        Logger.log("d",
                                   "DiscoverRepetierAction: reply_url: %s",
                                   reply.url())
                        if stream_url:  #not empty string or None
                            self._instance_supports_camera = True
                    if "webcams" in json_data:
                        Logger.log("d", "DiscoverRepetierAction: webcams: %s",
                                   len(json_data["webcams"]))
                        if len(json_data["webcams"]) > 0:
                            if "dynamicUrl" in json_data["webcams"][0]:
                                Logger.log(
                                    "d",
                                    "DiscoverRepetierAction: Checking streamurl"
                                )
                                stream_url = json_data["webcams"][0][
                                    "dynamicUrl"].replace(
                                        "127.0.0.1",
                                        re.findall(r'[0-9]+(?:\.[0-9]+){3}',
                                                   reply.url().toString())[0])
                                Logger.log(
                                    "d",
                                    "DiscoverRepetierAction: stream_url: %s",
                                    stream_url)
                                Logger.log(
                                    "d",
                                    "DiscoverRepetierAction: reply_url: %s",
                                    reply.url())
                                if stream_url:  #not empty string or None
                                    self._instance_supports_camera = True
                elif http_status_code == 401:
                    Logger.log("d", "Invalid API key for Repetier.")
                    self._instance_api_key_accepted = False
                    self._instance_in_error = True

                self._instance_responded = True
                self.selectedInstanceSettingsChanged.emit()

    def _createRequest(self,
                       url: str,
                       basic_auth_username: str = "",
                       basic_auth_password: str = "") -> QNetworkRequest:
        request = QNetworkRequest(url)
        request.setAttribute(QNetworkRequest.FollowRedirectsAttribute, True)
        request.setRawHeader(b"User-Agent", self._user_agent)

        if basic_auth_username and basic_auth_password:
            data = base64.b64encode(
                ("%s:%s" % (basic_auth_username,
                            basic_auth_password)).encode()).decode("utf-8")
            request.setRawHeader(b"Authorization",
                                 ("Basic %s" % data).encode())

        # ignore SSL errors (eg for self-signed certificates)
        ssl_configuration = QSslConfiguration.defaultConfiguration()
        ssl_configuration.setPeerVerifyMode(QSslSocket.VerifyNone)
        request.setSslConfiguration(ssl_configuration)

        return request

    ##  Utility handler to base64-decode a string (eg an obfuscated API key), if it has been encoded before
    def _deobfuscateString(self, source: str) -> str:
        try:
            return base64.b64decode(source.encode("ascii")).decode("ascii")
        except UnicodeDecodeError:
            return source

    def _getInstanceInfo(
        self, instance_id: str
    ) -> Tuple[Optional[RepetierOutputDevice], str, str, str]:
        if not self._network_plugin:
            return (None, "", "", "")
        instance = self._network_plugin.getInstanceById(instance_id)
        if not instance:
            return (None, "", "", "")

        return (instance, instance.baseURL, instance.getProperty("userName"),
                instance.getProperty("password"))
Exemplo n.º 54
0
class DiscoveredPrintersModel(QObject):
    def __init__(self,
                 application: "CuraApplication",
                 parent: Optional["QObject"] = None) -> None:
        super().__init__(parent)

        self._application = application
        self._discovered_printer_by_ip_dict = dict(
        )  # type: Dict[str, DiscoveredPrinter]

        self._plugin_for_manual_device = None  # type: Optional[OutputDevicePlugin]
        self._manual_device_address = ""

        self._manual_device_request_timeout_in_seconds = 5  # timeout for adding a manual device in seconds
        self._manual_device_request_timer = QTimer()
        self._manual_device_request_timer.setInterval(
            self._manual_device_request_timeout_in_seconds * 1000)
        self._manual_device_request_timer.setSingleShot(True)
        self._manual_device_request_timer.timeout.connect(
            self._onManualRequestTimeout)

    discoveredPrintersChanged = pyqtSignal()

    @pyqtSlot(str)
    def checkManualDevice(self, address: str) -> None:
        if self.hasManualDeviceRequestInProgress:
            Logger.log(
                "i",
                "A manual device request for address [%s] is still in progress, do nothing",
                self._manual_device_address)
            return

        priority_order = [
            ManualDeviceAdditionAttempt.PRIORITY,
            ManualDeviceAdditionAttempt.POSSIBLE,
        ]  # type: List[ManualDeviceAdditionAttempt]

        all_plugins_dict = self._application.getOutputDeviceManager(
        ).getAllOutputDevicePlugins()

        can_add_manual_plugins = [
            item for item in filter(
                lambda plugin_item: plugin_item.canAddManualDevice(address) in
                priority_order, all_plugins_dict.values())
        ]

        if not can_add_manual_plugins:
            Logger.log(
                "d",
                "Could not find a plugin to accept adding %s manually via address.",
                address)
            return

        plugin = max(
            can_add_manual_plugins,
            key=lambda p: priority_order.index(p.canAddManualDevice(address)))
        self._plugin_for_manual_device = plugin
        self._plugin_for_manual_device.addManualDevice(
            address, callback=self._onManualDeviceRequestFinished)
        self._manual_device_address = address
        self._manual_device_request_timer.start()
        self.hasManualDeviceRequestInProgressChanged.emit()

    @pyqtSlot()
    def cancelCurrentManualDeviceRequest(self) -> None:
        self._manual_device_request_timer.stop()

        if self._manual_device_address:
            if self._plugin_for_manual_device is not None:
                self._plugin_for_manual_device.removeManualDevice(
                    self._manual_device_address,
                    address=self._manual_device_address)
            self._manual_device_address = ""
            self._plugin_for_manual_device = None
            self.hasManualDeviceRequestInProgressChanged.emit()
            self.manualDeviceRequestFinished.emit(False)

    def _onManualRequestTimeout(self) -> None:
        Logger.log(
            "w",
            "Manual printer [%s] request timed out. Cancel the current request.",
            self._manual_device_address)
        self.cancelCurrentManualDeviceRequest()

    hasManualDeviceRequestInProgressChanged = pyqtSignal()

    @pyqtProperty(bool, notify=hasManualDeviceRequestInProgressChanged)
    def hasManualDeviceRequestInProgress(self) -> bool:
        return self._manual_device_address != ""

    manualDeviceRequestFinished = pyqtSignal(bool, arguments=["success"])

    def _onManualDeviceRequestFinished(self, success: bool,
                                       address: str) -> None:
        self._manual_device_request_timer.stop()
        if address == self._manual_device_address:
            self._manual_device_address = ""
            self.hasManualDeviceRequestInProgressChanged.emit()
            self.manualDeviceRequestFinished.emit(success)

    @pyqtProperty("QVariantMap", notify=discoveredPrintersChanged)
    def discoveredPrintersByAddress(self) -> Dict[str, DiscoveredPrinter]:
        return self._discovered_printer_by_ip_dict

    @pyqtProperty("QVariantList", notify=discoveredPrintersChanged)
    def discoveredPrinters(self) -> List["DiscoveredPrinter"]:
        item_list = list(x
                         for x in self._discovered_printer_by_ip_dict.values()
                         if not parseBool(x.device.getProperty("temporary")))

        # Split the printers into 2 lists and sort them ascending based on names.
        available_list = []
        not_available_list = []
        for item in item_list:
            if item.isUnknownMachineType or getattr(item.device, "clusterSize",
                                                    1) < 1:
                not_available_list.append(item)
            else:
                available_list.append(item)

        available_list.sort(key=lambda x: x.device.name)
        not_available_list.sort(key=lambda x: x.device.name)

        return available_list + not_available_list

    def addDiscoveredPrinter(self, ip_address: str, key: str, name: str,
                             create_callback: Callable[[str], None],
                             machine_type: str,
                             device: "NetworkedPrinterOutputDevice") -> None:
        if ip_address in self._discovered_printer_by_ip_dict:
            Logger.log("e", "Printer with ip [%s] has already been added",
                       ip_address)
            return

        discovered_printer = DiscoveredPrinter(ip_address,
                                               key,
                                               name,
                                               create_callback,
                                               machine_type,
                                               device,
                                               parent=self)
        self._discovered_printer_by_ip_dict[ip_address] = discovered_printer
        self.discoveredPrintersChanged.emit()

    def updateDiscoveredPrinter(self,
                                ip_address: str,
                                name: Optional[str] = None,
                                machine_type: Optional[str] = None) -> None:
        if ip_address not in self._discovered_printer_by_ip_dict:
            Logger.log("w", "Printer with ip [%s] is not known", ip_address)
            return

        item = self._discovered_printer_by_ip_dict[ip_address]

        if name is not None:
            item.setName(name)
        if machine_type is not None:
            item.setMachineType(machine_type)

    def removeDiscoveredPrinter(self, ip_address: str) -> None:
        if ip_address not in self._discovered_printer_by_ip_dict:
            Logger.log(
                "w",
                "Key [%s] does not exist in the discovered printers list.",
                ip_address)
            return

        del self._discovered_printer_by_ip_dict[ip_address]
        self.discoveredPrintersChanged.emit()

    # A convenience function for QML to create a machine (GlobalStack) out of the given discovered printer.
    # This function invokes the given discovered printer's "create_callback" to do this.
    @pyqtSlot("QVariant")
    def createMachineFromDiscoveredPrinter(
            self, discovered_printer: "DiscoveredPrinter") -> None:
        discovered_printer.create_callback(discovered_printer.getKey())
Exemplo n.º 55
0
class BeagleBoneHardware(QWidget):
    def __init__(self):
        super().__init__()

        self.allow_trigger = True  # Set this to false to stop triggering the laser
        self.triggers_sent = 0  # Will record the number of trigger pulses sent before pulsing was discontinued
        self.pulse_count_target = None
        self.trigger_timer = QTimer()
        self.trigger_timer.timeout.connect(self.trigger_pulse)
        self.home_target_dialog = HomeTargetsDialog(self)

        # Define limits/constants for substrate motion
        # FIXME: All limit values are guesses and need to be set.
        self.sub_bottom = 0
        self.sub_top = 40035  # Measured using a duty cycle test for full motion range.
        self.sub_position = 24000
        self.sub_goal = None
        self.sub_dir = 'up'
        self.sub_steps_per_rev = 1000
        self.sub_rps = 0.5
        self.sub_delay_us = 3

        self.current_target = 1
        self.target_goal = None
        self.target_position = 0
        self.target_pos_goal = None
        self.target_dir = 'ccw'
        self.target_steps_per_rev = 1000
        self.target_rps = 2
        self.target_delay_us = 1

        # Define class variables for
        self.out_pins = {
            "trigger": "P8_17",
            "sub_dir": "P9_28",
            "sub_step": "P9_27",
            "target_dir": "P9_30",
            "target_step": "P9_29"
        }
        self.in_pins = {"sub_home": "P8_9", "aux": "P9_15"}
        self.hi_pins = {"sub_home_hi": "P8_10"}

        # Create timers for 50% duty non-blocking motors
        # Target Axis
        self.target_on_timer = QTimer()
        self.target_on_timer.setSingleShot(True)
        self.target_off_timer = QTimer()
        self.target_off_timer.setSingleShot(True)
        # Substrate Axis
        self.sub_on_timer = QTimer()
        self.sub_on_timer.setSingleShot(True)
        self.sub_off_timer = QTimer()
        self.sub_off_timer.setSingleShot(True)

        self.setup_pins()

    def setup_pins(self):
        # Set up pins (all output pins default to low (0))
        for key in self.out_pins:
            GPIO.setup(self.out_pins[key], GPIO.OUT)
            GPIO.output(self.out_pins[key], GPIO.LOW)

        for key in self.in_pins:
            GPIO.setup(self.in_pins[key], GPIO.IN)

        for key in self.hi_pins:
            GPIO.setup(self.hi_pins[key], GPIO.OUT)
            GPIO.output(self.hi_pins[key], GPIO.HIGH)

    def start_pulsing(self, reprate, pulse_count=None):
        # Reset allow_trigger so that we don't end up breaking things/needing to restart the GUI on deposition cancel.
        print('Starting pulsing')
        rep_time = int(1000 / int(reprate))
        self.trigger_timer.start(rep_time)
        print('Started Timer with timeout of {}'.format(rep_time))
        self.pulse_count_target = pulse_count

    def stop_pulsing(self):
        self.trigger_timer.stop()
        print('Stop signal sent to timer')

    def trigger_pulse(self):
        if self.allow_trigger:
            GPIO.output(self.out_pins["trigger"], GPIO.HIGH)
            sleep(0.001)
            GPIO.output(self.out_pins["trigger"], GPIO.LOW)
            self.triggers_sent += 1

        if self.pulse_count_target is not None:
            print('Checking Pulse ({}) vs triggers sent ({})'.format(
                self.triggers_sent, self.pulse_count_target))
            if self.triggers_sent <= self.pulse_count_target:
                self.allow_trigger = False
                self.trigger_timer.stop()
                print(
                    'Stop signal sent. (Number of triggers ({}) >= target pulse count ({})'
                    .format(self.triggers_sent, self.pulse_count_target))

    def reset_trigger(self):
        print('Initiating trigger reset. Triggers sent: {}, Allow Trigger: {}'.
              format(self.triggers_sent, self.allow_trigger))
        self.allow_trigger = True
        self.triggers_sent = 0
        print('Triggering reset. Triggers sent: {}, Allow Trigger: {}'.format(
            self.triggers_sent, self.allow_trigger))

    def home_sub(self):
        self.sub_goal = 'home'

        if not GPIO.input(self.in_pins['sub_home']):
            if self.get_sub_dir() != 'up':
                self.set_sub_dir('up')
            self.step_sub()

    def move_sub_to(self, position):
        self.sub_goal = position

        if self.sub_goal > self.sub_position:
            self.set_sub_dir('up')
        elif self.sub_goal < self.sub_position:
            self.set_sub_dir('down')

        self.step_sub()

    def step_sub(self):
        # Define function for step parts (NOTE: driver sends step on GPIO.LOW)
        def step_start():
            if GPIO.input(self.in_pins['sub_home']):
                print(
                    'Started {} steps away from home. New home position set.'.
                    format(abs(self.sub_position)))
                self.sub_goal = 0
                self.sub_position = 0
                return
            elif not GPIO.input(self.in_pins['sub_home']):
                GPIO.output(self.out_pins['sub_step'], GPIO.HIGH)
                self.sub_off_timer.start(self.sub_delay_us)

        def step_finish():
            if self.get_sub_dir() == 'cw':
                self.sub_position += 1
            elif self.get_sub_dir() == 'ccw':
                self.sub_position -= 1
            GPIO.output(self.out_pins['sub_step'], GPIO.LOW)

            if self.sub_goal == 'home' and not GPIO.input(
                    self.in_pins['sub_home']):
                self.sub_on_timer.start(self.sub_delay_us)

        # Connect Timeouts
        self.sub_on_timer.timeout.connect(step_start)
        self.sub_off_timer.timeout.connect(step_finish)

        if self.sub_goal is None:
            if not GPIO.input(
                    self.in_pins['sub_home']) or self.sub_position > 0:
                self.sub_on_timer.start(self.sub_delay_us)
            elif self.sub_position == 0 or GPIO.input(
                    self.in_pins['sub_home']):
                # If the substrate is at end of range warn user and offer to rehome stage if there are issues.
                max_range = QMessageBox.question(
                    QWidget, 'Substrate End of Range',
                    'Press ok to continue or press reset to home the substrate',
                    QMessageBox.Ok | QMessageBox.Reset, QMessageBox.Ok)
                if max_range == QMessageBox.Ok:
                    pass
                elif max_range == QMessageBox.Reset:
                    self.home_sub()
        elif self.sub_goal is not None:
            if self.sub_goal != self.sub_position or self.sub_goal == 'home':
                self.sub_on_timer.start(self.sub_delay_us)
            elif self.sub_goal == self.sub_position:
                # Clear the goal position if the substrate is in that position
                self.sub_goal = None

    def set_sub_dir(self, direction):
        if direction.lower() == "up":
            # Set direction pin so that the substrate will be driven up (CCW)
            self.sub_dir = 'up'
            GPIO.output(self.out_pins['sub_dir'], GPIO.LOW)
        elif direction.lower() == "down":
            # Set direction pin so that the substrate will be driven down (CW)
            self.sub_dir = 'down'
            GPIO.output(self.out_pins['sub_dir'], GPIO.HIGH)
        else:
            print(
                'Invalid direction argument for the substrate motor supplied.')

    def get_sub_dir(self):
        return self.sub_dir

    def set_sub_speed(self, rps):
        self.sub_rps = rps
        # Calculate delay per step from speed
        self.sub_delay_us = round(
            ((self.sub_rps**-1) * (self.sub_steps_per_rev**-1)) / 2)

    def stop_sub(self):
        self.sub_goal = None

    def home_targets(self):
        self.home_target_dialog.exec_()

        if self.home_target_dialog.result() == QDialog.Accepted:
            self.target_position = 0
            self.current_target = 1
            target_home_info = QMessageBox.information(
                self, 'Home Set', 'New target carousel home position set',
                QMessageBox.Ok, QMessageBox.Ok)
        elif self.home_target_dialog.result() == QDialog.Rejected:
            target_home_info = QMessageBox.warning(
                self, 'Home Canceled',
                'Target carousel home cancelled by user.', QMessageBox.Ok,
                QMessageBox.Ok)

    def move_to_target(self, target_goal):
        self.target_goal = target_goal

        if self.target_goal != self.current_target:
            delta_pos = abs(target_goal - self.current_target)

            if delta_pos > 3:
                delta_pos = ((delta_pos - 3) * 2) - delta_pos

            if delta_pos < 0:
                self.set_target_dir('cw')
            elif delta_pos > 0:
                self.set_target_dir('ccw')
            else:
                print('Target {} already selected'.format(self.target_goal))
                return

            self.step_target()

    def step_target(self):
        # Define function for step parts (NOTE: driver sends step on GPIO.LOW)
        def step_start():
            GPIO.output(self.out_pins['target_step'], GPIO.HIGH)
            self.target_off_timer.start(self.target_delay_us)

        def step_finish():
            if self.get_target_dir() == 'cw':
                self.target_position += 1
            elif self.get_target_dir() == 'ccw':
                self.target_position -= 1
            self.target_position = self.target_position % 6000
            GPIO.output(self.out_pins['target_step'], GPIO.LOW)

        # Connect Timeouts
        self.target_on_timer.timeout.connect(step_start)
        self.target_off_timer.timeout.connect(step_finish)

        if self.target_goal is None and self.target_pos_goal is None:
            self.target_on_timer.start(self.target_delay_us)
        elif self.target_goal is not None:  # If there is a goal target
            # Set the target position goal if that has not already been done
            if self.target_pos_goal is None:
                self.target_pos_goal = 1000 * self.target_goal

            if self.target_position != self.target_pos_goal:
                self.target_on_timer.start(self.target_delay_us)
                # FIXME: Come up with logic so that the targets rotate the CW or CCW
                #  direction to minimize positioning time: WAIT DID I Already do that in the move to?
            elif self.target_position == self.target_pos_goal:
                self.target_pos_goal = None
                self.target_goal = None
                self.target_step_timer.stop()

    def set_target_dir(self, direction):
        if direction.lower() == "ccw":
            # Set direction pin so that the substrate will be driven up (CCW)
            self.target_dir = 'ccw'
            GPIO.output(self.out_pins['target_dir'], GPIO.LOW)
        elif direction.lower() == "cw":
            # Set direction pin so that the substrate will be driven down (CW)
            self.target_dir = 'cw'
            GPIO.output(self.out_pins['target_dir'], GPIO.HIGH)
        else:
            print('Invalid direction argument for the target motor supplied')

    def set_target_speed(self, rps):
        self.target_rps = rps
        # Calculate delay per step from speed
        self.target_delay_us = round(
            ((self.target_rps**-1) * (self.target_steps_per_rev**-1)) / 2)

    def get_target_dir(self):
        return self.target_dir

    def stop_target(self):
        self.target_goal = None
        self.target_pos_goal = None

    def __del__(self):
        GPIO.cleanup()
class SetColumnsDialog(QtWidgets.QDialog, FORM_CLASS):
    def __init__(self, parent=None):
        """Constructor."""
        super(SetColumnsDialog, self).__init__(parent)
        #self.setupUi(self)
        # Set up the user interface from Designer.
        # After setupUI you can access any designer object by doing
        # self.<objectname>, and you can use autoconnect slots - see
        # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html
        # #widgets-and-dialogs-with-auto-connect
        self.setupUi(self)
        p = self.palette()
        p.setColor(self.backgroundRole(), Qt.white)
        self.setPalette(p)
        self.resize(900,700)

        # attributes
        self.filename = None

        self.progress = QProgressBar(self)
        self.progress.setGeometry(20, 550, 480, 20)
        self.progress.setRange(0,0)
        self.progress.setVisible(False)

        self.progressInfo = QLabel(self)
        self.progressInfo.setText("Generando el reporte, por favor espere...")
        self.progressInfo.move(20, 570)
        newfont = QFont("Arial", 10)
        newfont.setItalic(True)
        self.progressInfo.setFont(newfont)
        self.progressInfo.setVisible(False)

    def showDialog(self, layer, columnList, fonafifoUrl):

        self.layer=layer
        self.columnList=columnList
        self.columnList.clear()
        self.fonafifoUrl = fonafifoUrl

        MAX_FOOTER = 600

        # FONAFIFO logo
        pic = QLabel(self)
        pic.setGeometry(600, MAX_FOOTER-30, 150, 50)
        pixmap = QPixmap()
        pixmap.load(fonafifoUrl);
        pic.setPixmap(pixmap)

        self.labelHeader = QLabel(self)
        self.labelHeader.setText("Selección de Criterios")
        self.labelHeader.setStyleSheet('color: #076F00')
        self.labelHeader.move(10, 20)
        newfont = QFont("Times", 20, QFont.Bold)
        self.labelHeader.setFont(newfont)

        self.frame = QFrame(self)
        self.frame.setFrameShape(QFrame.HLine)
        self.frame.setFrameShadow(QFrame.Sunken)
        self.frame.move(5,55);
        self.frame.resize(1955,5)


        self.buttonDescargar = QPushButton('Cargar Tabla', self)
        self.buttonDescargar.move(20, 590)
        self.buttonDescargar.resize(200, 30)
        self.buttonDescargar.clicked.connect(self.looping)

        self.buttonCerrar = QPushButton('Cerrar', self)
        self.buttonCerrar.move(270, 590)
        self.buttonCerrar.resize(200, 30)
        self.buttonCerrar.clicked.connect(self.close)

        X = 1500
        Y = 200

        HEIGHT=30
        WIDTH_LABEL=400
        INCREMENT_Y=20

        self.str_TOTAL = 'Total';
        self.str_AMPHIBIA = 'Amphibia';
        self.str_AVES = 'Aves';
        self.str_MAMMALIA = 'Mammalia';
        self.str_PLANTAE = 'Plantae';
        self.str_REPTILIA = 'Reptilia';
        self.str_REGISTROS_DE_PRESENCIA = 'Registros de presencia';
        self.str_AREAS_DE_DISTRIBUCION = 'Áreas de distribución';
        self.str_AMENAZADAS_UICN = 'Amenazadas IUCN';
        self.str_EN_PELIGRO_DE_EXTINCION = 'En peligro de extinción';
        self.str_CON_POBLACION_REDUCIDA = 'Con población reducida';
        self.str_VEDADA = 'Vedadas';
        self.str_CONT = ' --> ';

        self.list_class_pre = QListWidget(self)
        self.list_class_pre.addItem(self.str_TOTAL)
        self.list_class_pre.addItem(self.str_AMPHIBIA)
        self.list_class_pre.addItem(self.str_AVES)
        self.list_class_pre.addItem(self.str_MAMMALIA)
        self.list_class_pre.addItem(self.str_PLANTAE)
        self.list_class_pre.addItem(self.str_REPTILIA)
        self.list_class_pre.setCurrentRow(0)
        self.list_class_pre.setGeometry(10, 120, 150, 120)

        self.list_tipo_pre = QListWidget(self)
        self.list_tipo_pre.addItem(self.str_REGISTROS_DE_PRESENCIA)
        self.list_tipo_pre.addItem(self.str_AREAS_DE_DISTRIBUCION)
        self.list_tipo_pre.setCurrentRow(0)
        self.list_tipo_pre.setGeometry(10, 250, 150, 90)

        self.list_amenazas_pre = QListWidget(self)
        self.list_amenazas_pre.addItem("--")
        self.list_amenazas_pre.addItem(self.str_AMENAZADAS_UICN)
        self.list_amenazas_pre.addItem(self.str_EN_PELIGRO_DE_EXTINCION)
        self.list_amenazas_pre.addItem(self.str_CON_POBLACION_REDUCIDA)
        self.list_amenazas_pre.addItem(self.str_VEDADA)
        self.list_amenazas_pre.setCurrentRow(0)
        self.list_amenazas_pre.setGeometry(10, 350, 150, 90)

        self.botton_list_include = QPushButton(">>", self)
        self.botton_list_include.setGeometry(200, 120, 50, 50)
        self.botton_list_include.clicked.connect(self.chooseClassInclude)

        self.botton_list_exclude = QPushButton("<<", self)
        self.botton_list_exclude.setGeometry(200, 160, 50, 50)
        self.botton_list_exclude.clicked.connect(self.chooseClassExclude)

        self.list_class_post = QListWidget(self)
        self.list_class_post.setGeometry(300, 120, WIDTH_LABEL, 350)

        self.seleccionClass = "";
        self.seleccionTipo = "";
        self.seleccionAmenaza = "";













        self.checkbox_presencia_total_especies = QCheckBox("Riqueza total de especies por registros de presencia", self)
        self.checkbox_presencia_total_especies.setGeometry(X,Y,WIDTH_LABEL,HEIGHT)
        Y += INCREMENT_Y
        self.checkbox_presencia_total_especies_amenazadas = QCheckBox("Riqueza total de especies por registros de presencia - Amenazadas UICN", self)
        self.checkbox_presencia_total_especies_amenazadas.setGeometry(X, Y, WIDTH_LABEL, HEIGHT)
        Y += INCREMENT_Y
        self.checkbox_presencia_total_especies_amenazadas_lcvs = QCheckBox("Riqueza total de especies por registros de presencia - Amenazadas LCVS", self)
        self.checkbox_presencia_total_especies_amenazadas_lcvs.setGeometry(X, Y, WIDTH_LABEL, HEIGHT)
        Y += INCREMENT_Y
        Y += INCREMENT_Y
        self.checkbox_presencia_total_mammalia = QCheckBox("Riqueza total de MAMMALIA por registros de presencia", self)
        self.checkbox_presencia_total_mammalia.setGeometry(X, Y, WIDTH_LABEL, HEIGHT)
        Y += INCREMENT_Y
        self.checkbox_presencia_total_mammalia_amenazadas = QCheckBox("Riqueza total de MAMMALIA por registros de presencia - Amenazadas UICN", self)
        self.checkbox_presencia_total_mammalia_amenazadas.setGeometry(X, Y, WIDTH_LABEL, HEIGHT)
        Y += INCREMENT_Y
        self.checkbox_presencia_total_mammalia_amenazadas_lcvs = QCheckBox("Riqueza total de MAMMALIA por registros de presencia - Amenazadas LCVS", self)
        self.checkbox_presencia_total_mammalia_amenazadas_lcvs.setGeometry(X, Y, WIDTH_LABEL, HEIGHT)
        Y += INCREMENT_Y
        Y += INCREMENT_Y
        self.checkbox_presencia_total_aves = QCheckBox("Riqueza total de AVES por registros de presencia", self)
        self.checkbox_presencia_total_aves.setGeometry(X, Y, WIDTH_LABEL, HEIGHT)
        Y += INCREMENT_Y
        self.checkbox_presencia_total_aves_amenazadas = QCheckBox("Riqueza total de AVES por registros de presencia - Amenazadas UICN", self)
        self.checkbox_presencia_total_aves_amenazadas.setGeometry(X, Y, WIDTH_LABEL, HEIGHT)
        Y += INCREMENT_Y
        self.checkbox_presencia_total_aves_amenazadas_lcvs = QCheckBox("Riqueza total de AVES por registros de presencia - Amenazadas LCVS", self)
        self.checkbox_presencia_total_aves_amenazadas_lcvs.setGeometry(X, Y, WIDTH_LABEL, HEIGHT)
        Y += INCREMENT_Y
        Y += INCREMENT_Y
        self.checkbox_presencia_total_reptilia = QCheckBox("Riqueza total de REPTILIA por registros de presencia", self)
        self.checkbox_presencia_total_reptilia.setGeometry(X, Y, WIDTH_LABEL, HEIGHT)
        Y += INCREMENT_Y
        self.checkbox_presencia_total_reptilia_amenazadas = QCheckBox("Riqueza total de REPTILIA por registros de presencia - Amenazadas UICN", self)
        self.checkbox_presencia_total_reptilia_amenazadas.setGeometry(X, Y, WIDTH_LABEL, HEIGHT)
        Y += INCREMENT_Y
        self.checkbox_presencia_total_reptilia_amenazadas_lcvs = QCheckBox("Riqueza total de REPTILIA por registros de presencia - Amenazadas LCVS", self)
        self.checkbox_presencia_total_reptilia_amenazadas_lcvs.setGeometry(X, Y, WIDTH_LABEL, HEIGHT)
        Y += INCREMENT_Y
        Y += INCREMENT_Y
        self.checkbox_presencia_total_amphibia = QCheckBox("Riqueza total de AMPHIBIA por registros de presencia", self)
        self.checkbox_presencia_total_amphibia.setGeometry(X, Y, WIDTH_LABEL, HEIGHT)
        Y += INCREMENT_Y
        self.checkbox_presencia_total_amphibia_amenazadas = QCheckBox("Riqueza total de AMPHIBIA por registros de presencia - Amenazadas UICN", self)
        self.checkbox_presencia_total_amphibia_amenazadas.setGeometry(X, Y, WIDTH_LABEL, HEIGHT)
        Y += INCREMENT_Y
        self.checkbox_presencia_total_amphibia_amenazadas_lcvs = QCheckBox("Riqueza total de AMPHIBIA por registros de presencia - Amenazadas LCVS", self)
        self.checkbox_presencia_total_amphibia_amenazadas_lcvs.setGeometry(X, Y, WIDTH_LABEL, HEIGHT)
        Y += INCREMENT_Y
        Y += INCREMENT_Y
        self.checkbox_presencia_total_trees = QCheckBox("Riqueza total de PLANTAE por registros de presencia", self)
        self.checkbox_presencia_total_trees.setGeometry(X, Y, WIDTH_LABEL, HEIGHT)
        Y += INCREMENT_Y
        self.checkbox_presencia_total_trees_amenazadas = QCheckBox("Riqueza total de PLANTAE por registros de presencia - Amenazadas UICN", self)
        self.checkbox_presencia_total_trees_amenazadas.setGeometry(X, Y, WIDTH_LABEL, HEIGHT)
        Y += INCREMENT_Y
        self.checkbox_presencia_total_trees_amenazadas_lcvs = QCheckBox("Riqueza total de PLANTAE por registros de presencia - Amenazadas LCVS", self)
        self.checkbox_presencia_total_trees_amenazadas_lcvs.setGeometry(X, Y, WIDTH_LABEL, HEIGHT)

        Y = 70
        X = 1450

        self.checkbox_distribucion_total_especies = QCheckBox("Riqueza total de especies por áreas de distribución", self)
        self.checkbox_distribucion_total_especies.setGeometry(X, Y, WIDTH_LABEL, HEIGHT)
        Y += INCREMENT_Y
        self.checkbox_distribucion_total_especies_amenazadas = QCheckBox("Riqueza total de especies por áreas de distribución - Amenazadas UICN", self)
        self.checkbox_distribucion_total_especies_amenazadas.setGeometry(X, Y, WIDTH_LABEL, HEIGHT)
        Y += INCREMENT_Y
        self.checkbox_distribucion_total_especies_amenazadas_lcvs = QCheckBox("Riqueza total de especies por áreas de distribución - Amenazadas LCVS", self)
        self.checkbox_distribucion_total_especies_amenazadas_lcvs.setGeometry(X, Y, WIDTH_LABEL, HEIGHT)
        Y += INCREMENT_Y
        Y += INCREMENT_Y
        self.checkbox_distribucion_total_mammalia = QCheckBox("Riqueza total de MAMMALIA por áreas de distribución", self)
        self.checkbox_distribucion_total_mammalia.setGeometry(X, Y, WIDTH_LABEL, HEIGHT)
        Y += INCREMENT_Y
        self.checkbox_distribucion_total_mammalia_amenazadas = QCheckBox("Riqueza total de MAMMALIA por áreas de distribución - Amenazadas UICN", self)
        self.checkbox_distribucion_total_mammalia_amenazadas.setGeometry(X, Y, WIDTH_LABEL, HEIGHT)
        Y += INCREMENT_Y
        self.checkbox_distribucion_total_mammalia_amenazadas_lcvs = QCheckBox("Riqueza total de MAMMALIA por áreas de distribución - Amenazadas LCVS", self)
        self.checkbox_distribucion_total_mammalia_amenazadas_lcvs.setGeometry(X, Y, WIDTH_LABEL, HEIGHT)
        Y += INCREMENT_Y
        Y += INCREMENT_Y
        self.checkbox_distribucion_total_aves = QCheckBox("Riqueza total de AVES por áreas de distribución", self)
        self.checkbox_distribucion_total_aves.setGeometry(X, Y, WIDTH_LABEL, HEIGHT)
        Y += INCREMENT_Y
        self.checkbox_distribucion_total_aves_amenazadas = QCheckBox("Riqueza total de AVES por áreas de distribución - Amenazadas UICN", self)
        self.checkbox_distribucion_total_aves_amenazadas.setGeometry(X, Y, WIDTH_LABEL, HEIGHT)
        Y += INCREMENT_Y
        self.checkbox_distribucion_total_aves_amenazadas_lcvs = QCheckBox("Riqueza total de AVES por áreas de distribución - Amenazadas LCVS", self)
        self.checkbox_distribucion_total_aves_amenazadas_lcvs.setGeometry(X, Y, WIDTH_LABEL, HEIGHT)
        Y += INCREMENT_Y
        Y += INCREMENT_Y
        self.checkbox_distribucion_total_reptilia = QCheckBox("Riqueza total de REPTILIA por áreas de distribución", self)
        self.checkbox_distribucion_total_reptilia.setGeometry(X, Y, WIDTH_LABEL, HEIGHT)
        Y += INCREMENT_Y
        self.checkbox_distribucion_total_reptilia_amenazadas = QCheckBox("Riqueza total de REPTILIA por áreas de distribución - Amenazadas UICN", self)
        self.checkbox_distribucion_total_reptilia_amenazadas.setGeometry(X, Y, WIDTH_LABEL, HEIGHT)
        Y += INCREMENT_Y
        self.checkbox_distribucion_total_reptilia_amenazadas_lcvs = QCheckBox("Riqueza total de REPTILIA por áreas de distribución - Amenazadas LCVS", self)
        self.checkbox_distribucion_total_reptilia_amenazadas_lcvs.setGeometry(X, Y, WIDTH_LABEL, HEIGHT)
        Y += INCREMENT_Y
        Y += INCREMENT_Y
        self.checkbox_distribucion_total_amphibia = QCheckBox("Riqueza total de AMPHIBIA por áreas de distribución", self)
        self.checkbox_distribucion_total_amphibia.setGeometry(X, Y, WIDTH_LABEL, HEIGHT)
        Y += INCREMENT_Y
        self.checkbox_distribucion_total_amphibia_amenazadas = QCheckBox("Riqueza total de AMPHIBIA por áreas de distribución - Amenazadas UICN", self)
        self.checkbox_distribucion_total_amphibia_amenazadas.setGeometry(X, Y, WIDTH_LABEL, HEIGHT)
        Y += INCREMENT_Y
        self.checkbox_distribucion_total_amphibia_amenazadas_lcvs = QCheckBox("Riqueza total de AMPHIBIA por áreas de distribución - Amenazadas LCVS", self)
        self.checkbox_distribucion_total_amphibia_amenazadas_lcvs.setGeometry(X, Y, WIDTH_LABEL, HEIGHT)
        Y += INCREMENT_Y
        Y += INCREMENT_Y
        self.checkbox_distribucion_total_trees = QCheckBox("Riqueza total de PLANTAE por áreas de distribución", self)
        self.checkbox_distribucion_total_trees.setGeometry(X, Y, WIDTH_LABEL, HEIGHT)
        Y += INCREMENT_Y
        self.checkbox_distribucion_total_trees_amenazadas = QCheckBox("Riqueza total de PLANTAE por áreas de distribución - Amenazadas UICN", self)
        self.checkbox_distribucion_total_trees_amenazadas.setGeometry(X, Y, WIDTH_LABEL, HEIGHT)
        Y += INCREMENT_Y
        self.checkbox_distribucion_total_trees_amenazadas_lcvs = QCheckBox("Riqueza total de PLANTAE por áreas de distribución - Amenazadas LCVS", self)
        self.checkbox_distribucion_total_trees_amenazadas_lcvs.setGeometry(X, Y, WIDTH_LABEL, HEIGHT)






        self.show()

    def looping(self):

        self.progressInfo.setVisible(True)
        self.progress.setVisible(True)
        self.looptimer = QTimer()
        self.looptimer.setSingleShot(True)
        self.looptimer.timeout.connect(self.cargarTabla)
        self.looptimer.start(5000)





    def rellenarText(self, strClass):
        if (self.seleccionClass == strClass):
            if (self.seleccionTipo == self.str_REGISTROS_DE_PRESENCIA):
                const_REG_PRESENCIA = strClass + self.str_CONT + self.str_REGISTROS_DE_PRESENCIA
                if (self.seleccionAmenaza == '--'):
                    self.list_class_post.addItem(const_REG_PRESENCIA)
                if (self.seleccionAmenaza == self.str_AMENAZADAS_UICN):
                    self.list_class_post.addItem(const_REG_PRESENCIA + self.str_CONT + self.str_AMENAZADAS_UICN);
                if (self.seleccionAmenaza == self.str_EN_PELIGRO_DE_EXTINCION):
                    self.list_class_post.addItem(const_REG_PRESENCIA + self.str_CONT + self.str_EN_PELIGRO_DE_EXTINCION);
                if (self.seleccionAmenaza == self.str_CON_POBLACION_REDUCIDA):
                    self.list_class_post.addItem(const_REG_PRESENCIA + self.str_CONT + self.str_CON_POBLACION_REDUCIDA);
                if (self.seleccionAmenaza == self.str_VEDADA):
                    if((self.seleccionClass == self.str_PLANTAE) | (self.seleccionClass == self.str_TOTAL)):
                        self.list_class_post.addItem(const_REG_PRESENCIA + self.str_CONT + self.str_VEDADA);
                    else:
                        QMessageBox.about(self, 'Alerta!', 'La categoria "VEDADA" es exclusiva para Plantae');

                self.agregarColumnas(strClass, self.seleccionAmenaza, "occurrence");

            if (self.seleccionTipo == self.str_AREAS_DE_DISTRIBUCION):
                const_REG_DISTRIBUCION = strClass + self.str_CONT + self.str_AREAS_DE_DISTRIBUCION
                if (self.seleccionAmenaza == '--'):
                    self.list_class_post.addItem(const_REG_DISTRIBUCION)
                if (self.seleccionAmenaza == self.str_AMENAZADAS_UICN):
                    self.list_class_post.addItem(const_REG_DISTRIBUCION + self.str_CONT + self.str_AMENAZADAS_UICN);
                if (self.seleccionAmenaza == self.str_EN_PELIGRO_DE_EXTINCION):
                    self.list_class_post.addItem(const_REG_DISTRIBUCION + self.str_CONT + self.str_EN_PELIGRO_DE_EXTINCION);
                if (self.seleccionAmenaza == self.str_CON_POBLACION_REDUCIDA):
                    self.list_class_post.addItem(const_REG_DISTRIBUCION + self.str_CONT + self.str_CON_POBLACION_REDUCIDA);
                if (self.seleccionAmenaza == self.str_VEDADA):
                    if((self.seleccionClass == self.str_PLANTAE) | (self.seleccionClass == self.str_TOTAL)):
                        self.list_class_post.addItem(const_REG_DISTRIBUCION + self.str_CONT + self.str_VEDADA);
                    else:
                        QMessageBox.about(self, 'Alerta!', 'La categoria "VEDADA" es exclusiva para Plantae');

                self.agregarColumnas(strClass, self.seleccionAmenaza, "distribution");

    def agregarColumnas(self, strClass, strStatus, strType):
        if (strClass == self.str_TOTAL):
            if (strStatus == '--'):
                self.columnList.append("all_" + strType);
                self.columnList.append("all_" + strType + "_names");
            if (strStatus == self.str_AMENAZADAS_UICN):
                self.columnList.append("all_iucn_threatened_" + strType);
                self.columnList.append("all_iucn_threatened_" + strType + "_names");
            if (strStatus == self.str_EN_PELIGRO_DE_EXTINCION):
                self.columnList.append("all_lcvs_pe_" + strType);
                self.columnList.append("all_lcvs_pe_" + strType + "_names");
            if (strStatus == self.str_CON_POBLACION_REDUCIDA):
                self.columnList.append("all_lcvs_pr_" + strType);
                self.columnList.append("all_lcvs_pr_" + strType + "_names");
            if (strStatus == self.str_VEDADA):
                self.columnList.append("all_lcvs_ve_" + strType);
                self.columnList.append("all_lcvs_ve_" + strType + "_names");
        if (strClass == self.str_AMPHIBIA):
            if (strStatus == '--'):
                self.columnList.append("amphibia_" + strType);
                self.columnList.append("amphibia_" + strType + "_names");
            if (strStatus == self.str_AMENAZADAS_UICN):
                self.columnList.append("amphibia_iucn_threatened_" + strType);
                self.columnList.append("amphibia_iucn_threatened_" + strType + "_names");
            if (strStatus == self.str_EN_PELIGRO_DE_EXTINCION):
                self.columnList.append("amphibia_lcvs_pe_" + strType);
                self.columnList.append("amphibia_lcvs_pe_" + strType + "_names");
            if (strStatus == self.str_CON_POBLACION_REDUCIDA):
                self.columnList.append("amphibia_lcvs_pr_" + strType);
                self.columnList.append("amphibia_lcvs_pr_" + strType + "_names");
        if (strClass == self.str_AVES):
            if (strStatus == '--'):
                self.columnList.append("aves_" + strType);
                self.columnList.append("aves_occurrence_names");
            if (strStatus == self.str_AMENAZADAS_UICN):
                self.columnList.append("aves_iucn_threatened_" + strType);
                self.columnList.append("aves_iucn_threatened_" + strType + "_names");
            if (strStatus == self.str_EN_PELIGRO_DE_EXTINCION):
                self.columnList.append("aves_lcvs_pe_" + strType);
                self.columnList.append("aves_lcvs_pe_" + strType + "_names");
            if (strStatus == self.str_CON_POBLACION_REDUCIDA):
                self.columnList.append("aves_lcvs_pr_" + strType);
                self.columnList.append("aves_lcvs_pr_" + strType + "_names");
        if (strClass == self.str_MAMMALIA):
            if (strStatus == '--'):
                self.columnList.append("mammalia_" + strType);
                self.columnList.append("mammalia_" + strType + "_names");
            if (strStatus == self.str_AMENAZADAS_UICN):
                self.columnList.append("mammalia_iucn_threatened_" + strType);
                self.columnList.append("mammalia_iucn_threatened_" + strType + "_names");
            if (strStatus == self.str_EN_PELIGRO_DE_EXTINCION):
                self.columnList.append("mammalia_lcvs_pe_" + strType);
                self.columnList.append("mammalia_lcvs_pe_" + strType + "_names");
            if (strStatus == self.str_CON_POBLACION_REDUCIDA):
                self.columnList.append("mammalia_lcvs_pr_" + strType);
                self.columnList.append("mammalia_lcvs_pr_" + strType + "_names");
        if (strClass == self.str_PLANTAE):
            if (strStatus == '--'):
                self.columnList.append("plantae_" + strType);
                self.columnList.append("plantae_" + strType + "_names");
            if (strStatus == self.str_AMENAZADAS_UICN):
                self.columnList.append("plantae_iucn_threatened_" + strType);
                self.columnList.append("plantae_iucn_threatened_" + strType + "_names");
            if (strStatus == self.str_EN_PELIGRO_DE_EXTINCION):
                self.columnList.append("plantae_lcvs_pe_" + strType);
                self.columnList.append("plantae_lcvs_pe_" + strType + "_names");
            if (strStatus == self.str_CON_POBLACION_REDUCIDA):
                self.columnList.append("plantae_lcvs_pr_" + strType);
                self.columnList.append("plantae_lcvs_pr_" + strType + "_names");
            if (strStatus == self.str_VEDADA):
                self.columnList.append("plantae_lcvs_ve_" + strType);
                self.columnList.append("plantae_lcvs_ve_" + strType + "_names");
        if (strClass == self.str_REPTILIA):
            if (strStatus == '--'):
                self.columnList.append("reptilia_" + strType);
                self.columnList.append("reptilia_" + strType + "_names");
            if (strStatus == self.str_AMENAZADAS_UICN):
                self.columnList.append("reptilia_iucn_threatened_" + strType);
                self.columnList.append("reptilia_iucn_threatened_" + strType + "_names");
            if (strStatus == self.str_EN_PELIGRO_DE_EXTINCION):
                self.columnList.append("reptilia_lcvs_pe_" + strType);
                self.columnList.append("reptilia_lcvs_pe_" + strType + "_names");
            if (strStatus == self.str_CON_POBLACION_REDUCIDA):
                self.columnList.append("reptilia_lcvs_pr_" + strType);
                self.columnList.append("reptilia_lcvs_pr_" + strType + "_names");



    def chooseClassInclude(self):
        for selectedItem in self.list_class_pre.selectedItems():
            self.seleccionClass = self.list_class_pre.currentItem().text();
            self.seleccionTipo = self.list_tipo_pre.currentItem().text();
            self.seleccionAmenaza = self.list_amenazas_pre.currentItem().text();

            self.rellenarText(self.str_TOTAL);
            self.rellenarText(self.str_AMPHIBIA);
            self.rellenarText(self.str_AVES);
            self.rellenarText(self.str_MAMMALIA);
            self.rellenarText(self.str_PLANTAE);
            self.rellenarText(self.str_REPTILIA);


            #self.list_class_post.addItem(self.list_class_pre.currentItem().text())
            #self.list_class_pre.takeItem(self.list_class_pre.row(selectedItem))

    def chooseClassExclude(self):
        for selectedItem in self.list_class_post.selectedItems():
            self.list_class_pre.addItem(self.list_class_post.currentItem().text())
            self.list_class_post.takeItem(self.list_class_post.row(selectedItem))






    def cargarTabla(self):




        ###############################################################################
        ### VALIDACIONES
        ###############################################################################
        if self.checkbox_presencia_total_especies.isChecked():
            self.columnList.append("spp_all_richness_occurrence")
            self.columnList.append("spp_all_richness_occurrence_names")
        if self.checkbox_presencia_total_especies_amenazadas.isChecked():
            self.columnList.append("spp_all_threatened_richness_occurrence")
            self.columnList.append("spp_all_threatened_richness_occurrence_names")
        if self.checkbox_presencia_total_especies_amenazadas_lcvs.isChecked():
            self.columnList.append("spp_all_lcvs_richness_occurrence")
            self.columnList.append("spp_all_lcvs_richness_occurrence_names")

        if self.checkbox_presencia_total_mammalia.isChecked():
            self.columnList.append("spp_mammalia_richness_occurrence")
            self.columnList.append("spp_mammalia_richness_occurrence_names")
        if self.checkbox_presencia_total_mammalia_amenazadas.isChecked():
            self.columnList.append("spp_mammalia_threatened_richness_occurrence")
            self.columnList.append("spp_mammalia_threatened_richness_occurrence_names")
        if self.checkbox_presencia_total_mammalia_amenazadas_lcvs.isChecked():
            self.columnList.append("spp_mammalia_lcvs_richness_occurrence")
            self.columnList.append("spp_mammalia_lcvs_richness_occurrence_names")


        if self.checkbox_presencia_total_aves.isChecked():
            self.columnList.append("spp_aves_richness_occurrence")
            self.columnList.append("spp_aves_richness_occurrence_names")
        if self.checkbox_presencia_total_aves_amenazadas.isChecked():
            self.columnList.append("spp_aves_threatened_richness_occurrence")
            self.columnList.append("spp_aves_threatened_richness_occurrence_names")
        if self.checkbox_presencia_total_aves_amenazadas_lcvs.isChecked():
            self.columnList.append("spp_aves_lcvs_richness_occurrence")
            self.columnList.append("spp_aves_lcvs_richness_occurrence_names")

        if self.checkbox_presencia_total_reptilia.isChecked():
            self.columnList.append("spp_reptilia_richness_occurrence")
            self.columnList.append("spp_reptilia_richness_occurrence_names")
        if self.checkbox_presencia_total_reptilia_amenazadas.isChecked():
            self.columnList.append("spp_reptilia_threatened_richness_occurrence")
            self.columnList.append("spp_reptilia_threatened_richness_occurrence_names")
        if self.checkbox_presencia_total_reptilia_amenazadas_lcvs.isChecked():
            self.columnList.append("spp_reptilia_lcvs_richness_occurrence")
            self.columnList.append("spp_reptilia_lcvs_richness_occurrence_names")


        if self.checkbox_presencia_total_amphibia.isChecked():
            self.columnList.append("spp_amphibia_richness_occurrence")
            self.columnList.append("spp_amphibia_richness_occurrence_names")
        if self.checkbox_presencia_total_amphibia_amenazadas.isChecked():
            self.columnList.append("spp_amphibia_threatened_richness_occurrence")
            self.columnList.append("spp_amphibia_threatened_richness_occurrence_names")
        if self.checkbox_presencia_total_amphibia_amenazadas_lcvs.isChecked():
            self.columnList.append("spp_amphibia_lcvs_richness_occurrence")
            self.columnList.append("spp_amphibia_lcvs_richness_occurrence_names")


        if self.checkbox_presencia_total_trees.isChecked():
            self.columnList.append("spp_trees_richness_occurrence")
            self.columnList.append("spp_trees_richness_occurrence_names")
        if self.checkbox_presencia_total_trees_amenazadas.isChecked():
            self.columnList.append("spp_trees_threatened_richness_occurrence")
            self.columnList.append("spp_trees_threatened_richness_occurrence_names")
        if self.checkbox_presencia_total_trees_amenazadas_lcvs.isChecked():
            self.columnList.append("spp_trees_lcvs_richness_occurrence")
            self.columnList.append("spp_trees_lcvs_richness_occurrence_names")

        # DISTRIBUTION

        if self.checkbox_distribucion_total_especies.isChecked():
            self.columnList.append("spp_all_richness_distribution")
            self.columnList.append("spp_all_richness_distribution_names")
        if self.checkbox_distribucion_total_especies_amenazadas.isChecked():
            self.columnList.append("spp_all_threatened_richness_distribution")
            self.columnList.append("spp_all_threatened_richness_distribution_names")
        if self.checkbox_distribucion_total_especies_amenazadas_lcvs.isChecked():
            self.columnList.append("spp_all_lcvs_richness_distribution")
            self.columnList.append("spp_all_lcvs_richness_distribution_names")


        if self.checkbox_distribucion_total_mammalia.isChecked():
            self.columnList.append("spp_mammalia_richness_distribution")
            self.columnList.append("spp_mammalia_richness_distribution_names")
        if self.checkbox_distribucion_total_mammalia_amenazadas.isChecked():
            self.columnList.append("spp_mammalia_threatened_richness_distribution")
            self.columnList.append("spp_mammalia_threatened_richness_distribution_names")
        if self.checkbox_distribucion_total_mammalia_amenazadas_lcvs.isChecked():
            self.columnList.append("spp_mammalia_lcvs_richness_distribution")
            self.columnList.append("spp_mammalia_lcvs_richness_distribution_names")


        if self.checkbox_distribucion_total_aves.isChecked():
            self.columnList.append("spp_aves_richness_distribution")
            self.columnList.append("spp_aves_richness_distribution_names")
        if self.checkbox_distribucion_total_aves_amenazadas.isChecked():
            self.columnList.append("spp_aves_threatened_richness_distribution")
            self.columnList.append("spp_aves_threatened_richness_distribution_names")
        if self.checkbox_distribucion_total_aves_amenazadas_lcvs.isChecked():
            self.columnList.append("spp_aves_lcvs_richness_distribution")
            self.columnList.append("spp_aves_lcvs_richness_distribution_names")


        if self.checkbox_distribucion_total_reptilia.isChecked():
            self.columnList.append("spp_reptilia_richness_distribution")
            self.columnList.append("spp_reptilia_richness_distribution_names")
        if self.checkbox_distribucion_total_reptilia_amenazadas.isChecked():
            self.columnList.append("spp_reptilia_threatened_richness_distribution")
            self.columnList.append("spp_reptilia_threatened_richness_distribution_names")
        if self.checkbox_distribucion_total_reptilia_amenazadas_lcvs.isChecked():
            self.columnList.append("spp_reptilia_lcvs_richness_distribution")
            self.columnList.append("spp_reptilia_lcvs_richness_distribution_names")


        if self.checkbox_distribucion_total_amphibia.isChecked():
            self.columnList.append("spp_amphibia_richness_distribution")
            self.columnList.append("spp_amphibia_richness_distribution_names")
        if self.checkbox_distribucion_total_amphibia_amenazadas.isChecked():
            self.columnList.append("spp_amphibia_threatened_richness_distribution")
            self.columnList.append("spp_amphibia_threatened_richness_distribution_names")
        if self.checkbox_distribucion_total_amphibia_amenazadas_lcvs.isChecked():
            self.columnList.append("spp_amphibia_lcvs_richness_distribution")
            self.columnList.append("spp_amphibia_lcvs_richness_distribution_names")


        if self.checkbox_distribucion_total_trees.isChecked():
            self.columnList.append("spp_trees_richness_distribution")
            self.columnList.append("spp_trees_richness_distribution_names")
        if self.checkbox_distribucion_total_trees_amenazadas.isChecked():
            self.columnList.append("spp_trees_threatened_richness_distribution")
            self.columnList.append("spp_trees_threatened_richness_distribution_names")
        if self.checkbox_distribucion_total_trees_amenazadas_lcvs.isChecked():
            self.columnList.append("spp_trees_lcvs_richness_distribution")
            self.columnList.append("spp_trees_lcvs_richness_distribution_names")

        self.dlgIdentificarPoligono = IdentifyToolDialog()
        self.dlgIdentificarPoligono.showDialog(self.layer, self.columnList, self.progress, self.progressInfo, self.fonafifoUrl)
Exemplo n.º 57
0
class ElectrumGui(BaseElectrumGui, Logger):

    network_dialog: Optional['NetworkDialog']
    lightning_dialog: Optional['LightningDialog']
    watchtower_dialog: Optional['WatchtowerDialog']

    @profiler
    def __init__(self, *, config: 'SimpleConfig', daemon: 'Daemon',
                 plugins: 'Plugins'):
        set_language(config.get('language', get_default_language()))
        BaseElectrumGui.__init__(self,
                                 config=config,
                                 daemon=daemon,
                                 plugins=plugins)
        Logger.__init__(self)
        self.logger.info(
            f"Qt GUI starting up... Qt={QtCore.QT_VERSION_STR}, PyQt={QtCore.PYQT_VERSION_STR}"
        )
        # Uncomment this call to verify objects are being properly
        # GC-ed when windows are closed
        #network.add_jobs([DebugMem([Abstract_Wallet, SPV, Synchronizer,
        #                            ElectrumWindow], interval=5)])
        QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads)
        if hasattr(QtCore.Qt, "AA_ShareOpenGLContexts"):
            QtCore.QCoreApplication.setAttribute(
                QtCore.Qt.AA_ShareOpenGLContexts)
        if hasattr(QGuiApplication, 'setDesktopFileName'):
            QGuiApplication.setDesktopFileName('electrum.desktop')
        self.gui_thread = threading.current_thread()
        self.windows = []  # type: List[ElectrumWindow]
        self.efilter = OpenFileEventFilter(self.windows)
        self.app = QElectrumApplication(sys.argv)
        self.app.installEventFilter(self.efilter)
        self.app.setWindowIcon(read_QIcon("electrum.png"))
        self._cleaned_up = False
        # timer
        self.timer = QTimer(self.app)
        self.timer.setSingleShot(False)
        self.timer.setInterval(500)  # msec

        self.network_dialog = None
        self.lightning_dialog = None
        self.watchtower_dialog = None
        self.network_updated_signal_obj = QNetworkUpdatedSignalObject()
        self._num_wizards_in_progress = 0
        self._num_wizards_lock = threading.Lock()
        self.dark_icon = self.config.get("dark_icon", False)
        self.tray = None
        self._init_tray()
        self.app.new_window_signal.connect(self.start_new_window)
        self.app.quit_signal.connect(self.app.quit, Qt.QueuedConnection)
        self.set_dark_theme_if_needed()
        run_hook('init_qt', self)

    def _init_tray(self):
        self.tray = QSystemTrayIcon(self.tray_icon(), None)
        self.tray.setToolTip('Electrum')
        self.tray.activated.connect(self.tray_activated)
        self.build_tray_menu()
        self.tray.show()

    def set_dark_theme_if_needed(self):
        use_dark_theme = self.config.get('qt_gui_color_theme',
                                         'default') == 'dark'
        if use_dark_theme:
            try:
                import qdarkstyle
                self.app.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5())
            except BaseException as e:
                use_dark_theme = False
                self.logger.warning(f'Error setting dark theme: {repr(e)}')
        # Apply any necessary stylesheet patches
        patch_qt_stylesheet(use_dark_theme=use_dark_theme)
        # Even if we ourselves don't set the dark theme,
        # the OS/window manager/etc might set *a dark theme*.
        # Hence, try to choose colors accordingly:
        ColorScheme.update_from_widget(QWidget(), force_dark=use_dark_theme)

    def build_tray_menu(self):
        if not self.tray:
            return
        # Avoid immediate GC of old menu when window closed via its action
        if self.tray.contextMenu() is None:
            m = QMenu()
            self.tray.setContextMenu(m)
        else:
            m = self.tray.contextMenu()
            m.clear()
        network = self.daemon.network
        m.addAction(_("Network"), self.show_network_dialog)
        if network and network.lngossip:
            m.addAction(_("Lightning Network"), self.show_lightning_dialog)
        if network and network.local_watchtower:
            m.addAction(_("Local Watchtower"), self.show_watchtower_dialog)
        for window in self.windows:
            name = window.wallet.basename()
            submenu = m.addMenu(name)
            submenu.addAction(_("Show/Hide"), window.show_or_hide)
            submenu.addAction(_("Close"), window.close)
        m.addAction(_("Dark/Light"), self.toggle_tray_icon)
        m.addSeparator()
        m.addAction(_("Exit Electrum"), self.app.quit)

    def tray_icon(self):
        if self.dark_icon:
            return read_QIcon('electrum_dark_icon.png')
        else:
            return read_QIcon('electrum_light_icon.png')

    def toggle_tray_icon(self):
        if not self.tray:
            return
        self.dark_icon = not self.dark_icon
        self.config.set_key("dark_icon", self.dark_icon, True)
        self.tray.setIcon(self.tray_icon())

    def tray_activated(self, reason):
        if reason == QSystemTrayIcon.DoubleClick:
            if all([w.is_hidden() for w in self.windows]):
                for w in self.windows:
                    w.bring_to_top()
            else:
                for w in self.windows:
                    w.hide()

    def _cleanup_before_exit(self):
        if self._cleaned_up:
            return
        self._cleaned_up = True
        self.app.new_window_signal.disconnect()
        self.efilter = None
        # If there are still some open windows, try to clean them up.
        for window in list(self.windows):
            window.close()
            window.clean_up()
        if self.network_dialog:
            self.network_dialog.close()
            self.network_dialog.clean_up()
            self.network_dialog = None
        self.network_updated_signal_obj = None
        if self.lightning_dialog:
            self.lightning_dialog.close()
            self.lightning_dialog = None
        if self.watchtower_dialog:
            self.watchtower_dialog.close()
            self.watchtower_dialog = None
        # Shut down the timer cleanly
        self.timer.stop()
        self.timer = None
        # clipboard persistence. see http://www.mail-archive.com/[email protected]/msg17328.html
        event = QtCore.QEvent(QtCore.QEvent.Clipboard)
        self.app.sendEvent(self.app.clipboard(), event)
        if self.tray:
            self.tray.hide()
            self.tray.deleteLater()
            self.tray = None

    def _maybe_quit_if_no_windows_open(self) -> None:
        """Check if there are any open windows and decide whether we should quit."""
        # keep daemon running after close
        if self.config.get('daemon'):
            return
        # check if a wizard is in progress
        with self._num_wizards_lock:
            if self._num_wizards_in_progress > 0 or len(self.windows) > 0:
                return
        self.app.quit()

    def new_window(self, path, uri=None):
        # Use a signal as can be called from daemon thread
        self.app.new_window_signal.emit(path, uri)

    def show_lightning_dialog(self):
        if not self.daemon.network.has_channel_db():
            return
        if not self.lightning_dialog:
            self.lightning_dialog = LightningDialog(self)
        self.lightning_dialog.bring_to_top()

    def show_watchtower_dialog(self):
        if not self.watchtower_dialog:
            self.watchtower_dialog = WatchtowerDialog(self)
        self.watchtower_dialog.bring_to_top()

    def show_network_dialog(self):
        if self.network_dialog:
            self.network_dialog.on_update()
            self.network_dialog.show()
            self.network_dialog.raise_()
            return
        self.network_dialog = NetworkDialog(
            network=self.daemon.network,
            config=self.config,
            network_updated_signal_obj=self.network_updated_signal_obj)
        self.network_dialog.show()

    def _create_window_for_wallet(self, wallet):
        w = ElectrumWindow(self, wallet)
        self.windows.append(w)
        self.build_tray_menu()
        w.warn_if_testnet()
        w.warn_if_watching_only()
        return w

    def count_wizards_in_progress(func):
        def wrapper(self: 'ElectrumGui', *args, **kwargs):
            with self._num_wizards_lock:
                self._num_wizards_in_progress += 1
            try:
                return func(self, *args, **kwargs)
            finally:
                with self._num_wizards_lock:
                    self._num_wizards_in_progress -= 1
                self._maybe_quit_if_no_windows_open()

        return wrapper

    @count_wizards_in_progress
    def start_new_window(self,
                         path,
                         uri,
                         *,
                         app_is_starting=False) -> Optional[ElectrumWindow]:
        '''Raises the window for the wallet if it is open.  Otherwise
        opens the wallet and creates a new window for it'''
        wallet = None
        try:
            wallet = self.daemon.load_wallet(path, None)
        except Exception as e:
            self.logger.exception('')
            custom_message_box(icon=QMessageBox.Warning,
                               parent=None,
                               title=_('Error'),
                               text=_('Cannot load wallet') + ' (1):\n' +
                               repr(e))
            # if app is starting, still let wizard to appear
            if not app_is_starting:
                return
        if not wallet:
            try:
                wallet = self._start_wizard_to_select_or_create_wallet(path)
            except (WalletFileException, BitcoinException) as e:
                self.logger.exception('')
                custom_message_box(icon=QMessageBox.Warning,
                                   parent=None,
                                   title=_('Error'),
                                   text=_('Cannot load wallet') + ' (2):\n' +
                                   repr(e))
        if not wallet:
            return
        # create or raise window
        try:
            for window in self.windows:
                if window.wallet.storage.path == wallet.storage.path:
                    break
            else:
                window = self._create_window_for_wallet(wallet)
        except Exception as e:
            self.logger.exception('')
            custom_message_box(icon=QMessageBox.Warning,
                               parent=None,
                               title=_('Error'),
                               text=_('Cannot create window for wallet') +
                               ':\n' + repr(e))
            if app_is_starting:
                wallet_dir = os.path.dirname(path)
                path = os.path.join(wallet_dir,
                                    get_new_wallet_name(wallet_dir))
                self.start_new_window(path, uri)
            return
        if uri:
            window.pay_to_URI(uri)
        window.bring_to_top()
        window.setWindowState(window.windowState() & ~QtCore.Qt.WindowMinimized
                              | QtCore.Qt.WindowActive)

        window.activateWindow()
        return window

    def _start_wizard_to_select_or_create_wallet(
            self, path) -> Optional[Abstract_Wallet]:
        wizard = InstallWizard(self.config,
                               self.app,
                               self.plugins,
                               gui_object=self)
        try:
            path, storage = wizard.select_storage(path, self.daemon.get_wallet)
            # storage is None if file does not exist
            if storage is None:
                wizard.path = path  # needed by trustedcoin plugin
                wizard.run('new')
                storage, db = wizard.create_storage(path)
            else:
                db = WalletDB(storage.read(), manual_upgrades=False)
                wizard.run_upgrades(storage, db)
        except (UserCancelled, GoBack):
            return
        except WalletAlreadyOpenInMemory as e:
            return e.wallet
        finally:
            wizard.terminate()
        # return if wallet creation is not complete
        if storage is None or db.get_action():
            return
        wallet = Wallet(db, storage, config=self.config)
        wallet.start_network(self.daemon.network)
        self.daemon.add_wallet(wallet)
        return wallet

    def close_window(self, window: ElectrumWindow):
        if window in self.windows:
            self.windows.remove(window)
        self.build_tray_menu()
        # save wallet path of last open window
        if not self.windows:
            self.config.save_last_wallet(window.wallet)
        run_hook('on_close_window', window)
        self.daemon.stop_wallet(window.wallet.storage.path)

    def init_network(self):
        # Show network dialog if config does not exist
        if self.daemon.network:
            if self.config.get('auto_connect') is None:
                wizard = InstallWizard(self.config,
                                       self.app,
                                       self.plugins,
                                       gui_object=self)
                wizard.init_network(self.daemon.network)
                wizard.terminate()

    def main(self):
        # setup Ctrl-C handling and tear-down code first, so that user can easily exit whenever
        self.app.setQuitOnLastWindowClosed(
            False)  # so _we_ can decide whether to quit
        self.app.lastWindowClosed.connect(self._maybe_quit_if_no_windows_open)
        self.app.aboutToQuit.connect(self._cleanup_before_exit)
        signal.signal(signal.SIGINT, lambda *args: self.app.quit())
        # hook for crash reporter
        Exception_Hook.maybe_setup(config=self.config)
        # first-start network-setup
        try:
            self.init_network()
        except UserCancelled:
            return
        except GoBack:
            return
        except Exception as e:
            self.logger.exception('')
            return
        # start wizard to select/create wallet
        self.timer.start()
        path = self.config.get_wallet_path(use_gui_last_wallet=True)
        try:
            if not self.start_new_window(
                    path, self.config.get('url'), app_is_starting=True):
                return
        except Exception as e:
            self.logger.error(
                "error loading wallet (or creating window for it)")
            send_exception_to_crash_reporter(e)
            # Let Qt event loop start properly so that crash reporter window can appear.
            # We will shutdown when the user closes that window, via lastWindowClosed signal.
        # main loop
        self.logger.info("starting Qt main loop")
        self.app.exec_()
        # on some platforms the exec_ call may not return, so use _cleanup_before_exit

    def stop(self):
        self.logger.info('closing GUI')
        self.app.quit_signal.emit()
Exemplo n.º 58
0
class QEAppController(QObject):
    userNotify = pyqtSignal(str)

    def __init__(self, qedaemon):
        super().__init__()
        self.logger = get_logger(__name__)

        self._qedaemon = qedaemon

        # set up notification queue and notification_timer
        self.user_notification_queue = queue.Queue()
        self.user_notification_last_time = 0

        self.notification_timer = QTimer(self)
        self.notification_timer.setSingleShot(False)
        self.notification_timer.setInterval(500)  # msec
        self.notification_timer.timeout.connect(self.on_notification_timer)

        self._qedaemon.walletLoaded.connect(self.on_wallet_loaded)

        self.userNotify.connect(self.notifyAndroid)

    def on_wallet_loaded(self):
        qewallet = self._qedaemon.currentWallet
        if not qewallet:
            return
        # attach to the wallet user notification events
        # connect only once
        try:
            qewallet.userNotify.disconnect(self.on_wallet_usernotify)
        except:
            pass
        qewallet.userNotify.connect(self.on_wallet_usernotify)

    def on_wallet_usernotify(self, wallet, message):
        self.logger.debug(message)
        self.user_notification_queue.put(message)
        if not self.notification_timer.isActive():
            self.logger.debug('starting app notification timer')
            self.notification_timer.start()

    def on_notification_timer(self):
        if self.user_notification_queue.qsize() == 0:
            self.logger.debug('queue empty, stopping app notification timer')
            self.notification_timer.stop()
            return
        now = time.time()
        rate_limit = 20  # seconds
        if self.user_notification_last_time + rate_limit > now:
            return
        self.user_notification_last_time = now
        self.logger.info("Notifying GUI about new user notifications")
        try:
            self.userNotify.emit(self.user_notification_queue.get_nowait())
        except queue.Empty:
            pass

    def notifyAndroid(self, message):
        try:
            # TODO: lazy load not in UI thread please
            global notification
            if not notification:
                from plyer import notification
            icon = (os.path.dirname(os.path.realpath(__file__)) +
                    '/../icons/electrum.png')
            notification.notify('Electrum',
                                message,
                                app_icon=icon,
                                app_name='Electrum')
        except ImportError:
            self.logger.warning(
                'Notification: needs plyer; `sudo python3 -m pip install plyer`'
            )
        except Exception as e:
            self.logger.error(repr(e))

    @pyqtSlot(str, str)
    def doShare(self, data, title):
        #if platform != 'android':
        #return
        try:
            from jnius import autoclass, cast
        except ImportError:
            self.logger.error('Share: needs jnius. Platform not Android?')
            return

        JS = autoclass('java.lang.String')
        Intent = autoclass('android.content.Intent')
        sendIntent = Intent()
        sendIntent.setAction(Intent.ACTION_SEND)
        sendIntent.setType("text/plain")
        sendIntent.putExtra(Intent.EXTRA_TEXT, JS(data))
        pythonActivity = autoclass('org.kivy.android.PythonActivity')
        currentActivity = cast('android.app.Activity',
                               pythonActivity.mActivity)
        it = Intent.createChooser(sendIntent,
                                  cast('java.lang.CharSequence', JS(title)))
        currentActivity.startActivity(it)

    @pyqtSlot('QString')
    def textToClipboard(self, text):
        QGuiApplication.clipboard().setText(text)

    @pyqtSlot(result='QString')
    def clipboardToText(self):
        return QGuiApplication.clipboard().text()
Exemplo n.º 59
0
class login_window(QWidget, Ui_LoginForm):
    def __init__(self, mode=0, parent=None):
        super(login_window, self).__init__(parent)
        self.mode = mode
        self.setupUi(self)

        self.setWindowTitle("登陆")
        self.setWindowIcon(QtGui.QIcon('./resource/favicon.ico'))

        ######  登录页面头图设置 完美显示图片,并自适应大小
        pix = QtGui.QPixmap("./resource/bg7699.png")
        self.login_top_bg_label.setPixmap(pix)
        self.login_top_bg_label.setScaledContents(True)

        ###### 显示窗口在屏幕中间
        self.center()

        ###### 初始化登录信息
        self.init_login_info()

        ###### 自动登录
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.goto_autologin)
        self.timer.setSingleShot(True)
        self.timer.start(1000)

        ###### 创建资产文件夹
        try:
            os.mkdir('Assets_Host')
            os.mkdir('Assets_URL')
            os.mkdir('log')
        except Exception as e:
            pass

        ###### 检查配置文件
        try:
            with open('config.ini') as content:
                pass
            self.read_config()
        except Exception as e:
            output_log("[!] 配置文件未找到,初次使用请配置扫描器信息...")
            settings = QSettings("config.ini", QSettings.IniFormat)
            settings.setValue("host", "")
            settings.setValue("port", "")
            settings.setValue("account", "")
            settings.setValue("password", "")
            settings.setValue("remeberpassword", "")
            settings.setValue("autologin", "")
            self.open_config_pane()

    ###### 显示窗口在屏幕中间
    def center(self):
        qr = self.frameGeometry()
        cp = QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())

    ###### 从配置文件取出扫描器的配置信息
    def read_config(self):
        settings = QSettings("config.ini", QSettings.IniFormat)
        self.host = settings.value("host")
        self.port = settings.value("port")
        global SCANNER_URL
        if self.port == '443':
            SCANNER_URL = f'https://{self.host}'
        else:
            SCANNER_URL = f'https://{self.host}:{self.port}'

    ###### 跳转配置信息页面
    def open_config_pane(self):
        dialog = configdialog()
        if dialog.exec_() == QDialog.Accepted:
            output_log("[+] 扫描器配置信息保存成功。")
            self.read_config()

    ###### 自动登录 and 记住密码 联动
    def auto_login(self, checked):
        if checked:
            self.remember_passwd_checkBox.setChecked(True)

    ###### 记住密码 and 自动登录 联动
    def remember_pwd(self, checked):
        if not checked:
            self.Auto_login_checkBox.setChecked(False)

    ###### 账户 and 密码 and 登录按钮  有效性联动
    def enable_login_btn(self):
        account = self.username_lineEdit.text()
        passwd = self.passwd_lineEdit.text()
        if len(account) > 0 and len(passwd) > 0:
            self.login_pushButton.setEnabled(True)
        else:
            self.login_pushButton.setEnabled(False)

    ###### 登录按钮事件
    def ckeck_login(self):
        self.on_pushButton_enter_clicked()

    ###### 网页登录按钮事件
    def open_url_link(self):
        QDesktopServices.openUrl(QUrl(SCANNER_URL))

    ###### 自动登录
    def goto_autologin(self):
        if self.Auto_login_checkBox.isChecked() == True and self.mode == 0:
            self.on_pushButton_enter_clicked()

    ###### 保存登录信息
    def save_login_info(self):
        settings = QSettings("config.ini", QSettings.IniFormat)
        settings.setValue("host", self.host)
        settings.setValue("port", self.port)
        settings.setValue("account", self.username_lineEdit.text())
        settings.setValue("password", self.passwd_lineEdit.text())
        settings.setValue("remeberpassword",
                          self.remember_passwd_checkBox.isChecked())
        settings.setValue("autologin", self.Auto_login_checkBox.isChecked())

    ###### 初始化登录信息
    def init_login_info(self):
        settings = QSettings("config.ini", QSettings.IniFormat)
        the_account = settings.value("account")
        the_password = settings.value("password")
        the_remeberpassword = settings.value("remeberpassword")
        the_autologin = settings.value("autologin")
        self.username_lineEdit.setText(the_account)
        if the_remeberpassword == "true" or the_remeberpassword == True:
            self.remember_passwd_checkBox.setChecked(True)
            self.passwd_lineEdit.setText(the_password)
        if the_autologin == "true" or the_autologin == True:
            self.Auto_login_checkBox.setChecked(True)

    windowList = []

    ###### 登录事件执行
    def on_pushButton_enter_clicked(self):
        self.username = self.username_lineEdit.text()
        self.passwd = self.passwd_lineEdit.text()
        output_log(
            f'[+] 配置信息读取成功,用户名:{self.username} 扫描器地址:{self.host} 扫描器端口:{self.port}'
        )
        try:
            cooker = RSAS_Requests.RSAS_Login(SCANNER_URL, self.username,
                                              self.passwd)
            if cooker.headers['location'] == '/':
                self.save_login_info()
                output_log('[+] 扫描器登录成功!')
                try:
                    output_log('[+] 正在连接扫描器获取参数,初始化主页面...')
                    task_window = main_window(SCANNER_URL, self.username,
                                              self.passwd)
                    self.windowList.append(task_window)
                    self.close()
                    task_window.show()
                except Exception as e:
                    output_log('[!] 主页面初始化失败!')
                    QtWidgets.QMessageBox.information(
                        None, "初始化失败!", f"{e}",
                        QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
                        QtWidgets.QMessageBox.Yes)
        except Exception as e:
            output_log('[!] 扫描器登录失败!')
            QtWidgets.QMessageBox.about(None, '登录失败!', '密码错误!')
Exemplo n.º 60
0
class CuraSplashScreen(QSplashScreen):
    def __init__(self):
        super().__init__()
        self._scale = 0.7

        splash_image = QPixmap(Resources.getPath(Resources.Images, "cura.png"))
        self.setPixmap(splash_image)

        self._current_message = ""

        self._loading_image_rotation_angle = 0

        self._to_stop = False
        self._change_timer = QTimer()
        self._change_timer.setInterval(50)
        self._change_timer.setSingleShot(False)
        self._change_timer.timeout.connect(self.updateLoadingImage)

    def show(self):
        super().show()
        self._change_timer.start()

    def updateLoadingImage(self):
        if self._to_stop:
            return

        self._loading_image_rotation_angle -= 10
        self.repaint()

    # Override the mousePressEvent so the splashscreen doesn't disappear when clicked
    def mousePressEvent(self, mouse_event):
        pass

    def drawContents(self, painter):
        if self._to_stop:
            return

        painter.save()
        painter.setPen(QColor(255, 255, 255, 255))
        painter.setRenderHint(QPainter.Antialiasing)
        painter.setRenderHint(QPainter.Antialiasing, True)

        version = Application.getInstance().getVersion().split("-")
        buildtype = Application.getInstance().getBuildType()
        if buildtype:
            version[0] += " (%s)" % buildtype

        # draw version text
        font = QFont()  # Using system-default font here
        font.setPixelSize(37)
        painter.setFont(font)
        painter.drawText(215, 66, 330 * self._scale, 230 * self._scale,
                         Qt.AlignLeft | Qt.AlignTop, version[0])
        if len(version) > 1:
            font.setPointSize(12)
            painter.setFont(font)
            painter.setPen(QColor(200, 200, 200, 255))
            painter.drawText(247, 105, 330 * self._scale, 255 * self._scale,
                             Qt.AlignLeft | Qt.AlignTop, version[1])
        painter.setPen(QColor(255, 255, 255, 255))

        # draw the loading image
        pen = QPen()
        pen.setWidth(6 * self._scale)
        pen.setColor(QColor(32, 166, 219, 255))
        painter.setPen(pen)
        painter.drawArc(60, 150, 32 * self._scale, 32 * self._scale,
                        self._loading_image_rotation_angle * 16, 300 * 16)

        # draw message text
        if self._current_message:
            font = QFont()  # Using system-default font here
            font.setPixelSize(13)
            pen = QPen()
            pen.setColor(QColor(255, 255, 255, 255))
            painter.setPen(pen)
            painter.setFont(font)
            painter.drawText(100, 128, 170, 64,
                             Qt.AlignLeft | Qt.AlignVCenter | Qt.TextWordWrap,
                             self._current_message)

        painter.restore()
        super().drawContents(painter)

    def showMessage(self, message, *args, **kwargs):
        if self._to_stop:
            return

        self._current_message = message
        self.messageChanged.emit(message)
        QCoreApplication.flush()
        self.repaint()

    def close(self):
        # set stop flags
        self._to_stop = True
        self._change_timer.stop()
        super().close()