Пример #1
0
    def __init__(self, parent):
        super(QWidget, self).__init__(parent)

        self.container = parent
        self.tabWidget = PanelTab(self)

        self.panelSplit = pyqtSignal(object)
        self.panelFloat = pyqtSignal()
        self.panelClosed = pyqtSignal()
        self.tabClosed = pyqtSignal(QWidget, QString)
        self.panelMenuTriggered = pyqtSignal(PanelWidget, QAction)

        self.topLeftCorner = PanelTopLeftCorner(self)
        self.tabWidget.setCornerWidget(self.topLeftCorner, Qt.TopLeftCorner)
        self.topRightCorner = PanelTopRightCorner(self)
        self.tabWidget.setCornerWidget(self.topRightCorner, Qt.TopRightCorner)

        self.tabProxyStyle = TabProxyStyle('fusion')
        self.tabWidget.setStyle(self.tabProxyStyle)

        self.topRightCorner.floatClicked.connect(lambda: self.floatRequested())
        self.topRightCorner.closeClicked.connect(lambda: self.closeRequested())
        self.topRightCorner.splitClicked.connect(partial(self.splitRequested, Qt.PrimaryOrientation))

        mainLayout = QGridLayout(self)
        mainLayout.addWidget(self.tabWidget)
        mainLayout.setContentsMargins(2, 2, 2, 2)

        self.setMouseTracking(True)
        self.setAttribute(Qt.WA_DeleteOnClose)
        self.setAttribute(Qt.WA_Hover)
        self.installEventFilter(self)
Пример #2
0
    def __init__(self, QWidget_parent=None):
        QObject.__init__(self, QWidget_parent)
        self._minSpanX = 500
        self._minSpanY = 1

        self.Scale = SimplePlotterScale(Scale(0, 86399999), Scale(0, 10000))
        self.Scale0 = SimplePlotterScale(Scale(0, 86399999), Scale(_max, _min))

        self.changeScaleX = pyqtSignal(Scale, name='changeScaleX')
        self.changeScaleY = pyqtSignal(Scale, name='changeScaleY')
    def __init__(self, name, lower, upper, step_size, default):
        super(SliderWidget, self).__init__()
        self.valueChanged = pyqtSignal()
        self.lower = lower
        self.upper = upper
        self.step_size = step_size
        self.default = default
        self.name = name
        self.internal_steps = abs(upper - lower / step_size)
        self.slider = create_horizontal_slider(0, self.internal_steps, 1, self.to_internal_coordinate(default)).slider

        textfield = PyQt5.QtWidgets.QDoubleSpinBox()
        textfield.setRange(lower, upper)
        textfield.setSingleStep(step_size)
        textfield.setValue(default)

        label = PyQt5.QtWidgets.QLabel()
        label.setText(name + ": ")

        single_slider_layout = PyQt5.QtWidgets.QBoxLayout(PyQt5.QtWidgets.QBoxLayout.LeftToRight)
        single_slider_layout.addWidget(self.label)
        single_slider_layout.addWidget(self.slider)
        single_slider_layout.addWidget(self.textfield)
        self.setLayout(self.SingleSlidersLayout)

        textfield.valueChanged.connect(self.textfield_value_changed)
        self.slider.valueChanged.connect(self.slider_value_changed)
    def __init__(self, name, options, slot=None, default=None):
        super(ComboBoxWidget, self).__init__()
        self.activated = pyqtSignal()

        # ComboBox itself
        self.combobox = QtWidgets.QComboBox()
        self.combobox.orientationCombo = PyQt5.QtWidgets.QComboBox()
        self.combobox.setFixedWidth(220)

        # Label
        self.label = QtWidgets.QLabel()
        self.label.setText(name + ": ")

        self.SingleCheckBoxLayout = QBoxLayout(QBoxLayout.LeftToRight)
        self.SingleCheckBoxLayout.addWidget(self.label)
        self.SingleCheckBoxLayout.addWidget(self.combobox, Qt.AlignRight)
        self.setLayout(self.SingleCheckBoxLayout)
        self.setFixedHeight(70)
        self.setFlat(True)

        # options
        for i in options:
            self.add_item(i)

        if default is not None:
            index = self.combobox.findText(default)
            if index != -1:
                self.combobox.setCurrentIndex(index)

        if slot is not None:
            self.combobox.activated.connect(slot)
Пример #5
0
    def __init__(self, parent):
        super(QWidget, self).__init__(parent)

        self.vSplitRect = QRect()
        self.hSplitRect = QRect()
        self.floatRect = QRect()
        self.closeRect = QRect()

        self.closeClicked = pyqtSignal()
        self.floatClicked = pyqtSignal()
        self.splitClicked = pyqtSignal()

        self.mouseHoverState = MOUSE_HOVER.HOVER_NONE
        self.mouseClickPos = QPoint()
        self.setMouseTracking(True)
        self.setAttribute(Qt.WA_Hover)
        self.installEventFilter(self)
Пример #6
0
    def __init__(self, parent=None):
        QtWidgets.QDialog.__init__(self, parent)
        self.ui = uic.loadUi("client.ui", self)
        self.ui.show()

        self.signal = pyqtSignal()
        self.signal.connect(self.on_recv)
        self.thread = ChatThread(s, self.signal)
        self.thread.start()
Пример #7
0
def create_pyqt_class(metaobject):
    class_name = str(metaobject.className())
    cls = pyqt_classes.get(class_name)
    if cls:
        return cls
    attrs = {}

    properties = attrs["__properties__"] = {}
    for i in range(metaobject.propertyCount()):
        prop = PyQtProperty(metaobject.property(i))
        #import pdb; pdb.set_trace()
        prop_name = str(prop.name)
        #prop_name = prop_name[0].upper() + prop_name[1:]
        if prop.read_only:
            # XXX: write set-method which raises an error
            properties[prop_name] = attrs[prop_name] = property(prop.get, doc=prop.__doc__)
        else:
            properties[prop_name] = attrs[prop_name] = property(
                                                      prop.get, prop.set, doc=prop.__doc__)

    methods = attrs["__methods__"] = {}
    signals = attrs["__signals__"] = {}
    for i in range(metaobject.methodCount()):
        meta_method = metaobject.method(i)
        if meta_method.methodType() != QMetaMethod.Signal :
            method = PyQtMethod(meta_method)
            method_name = method.name
            if method_name in attrs:
                # There is already a property with this name
                # So append an underscore 
                method_name += "_"
            instance_method = method.instancemethod()
            instance_method.__doc__ = method.__doc__
            methods[method_name] = attrs[method_name] = instance_method 
        else :
            method_name = meta_method.name()
            signal_attrs = []
            #for i in range(meta_method.parameterCount()):
            #    typ = meta_method.parameterType(i)
            #    signal_attrs.append(typ)
            #import pdb; pdb.set_trace()
            # TODO: bind the signal (which is now unbound) to the c++ signal (we can bind them signal to signal, if signal to slot does not work)
            # TODO: make sure that the signal shows up as attribute in the created class
            properties[bytes(method_name).decode('ascii')] = pyqtSignal(meta_method.parameterTypes())

    # import pdb; pdb.set_trace()

    # Python is great :)
    # It can dynamically create a class with a base class and a dictionary
    cls = type(class_name, (PyQtClass,), attrs)
    pyqt_classes[class_name] = cls
    return cls
    def __init__(self, name, lower, upper, step_size, default, slot, float_flag):
        super(SliderWidget, self).__init__()
        self.valueChanged = pyqtSignal()
        self.internal_steps = abs(upper - lower) / step_size

        print("Default " + str(default))

        def to_internal_coordinate(value):
            return (self.internal_steps / (upper - lower)) * (value - lower)

        def to_external_coordinate(value):
            return lower + (value * (upper - lower)) / self.internal_steps

        # Slider itself
        self.slider = \
            Slider(0, self.internal_steps, 1, to_internal_coordinate(default)).slider

        # Textfield
        if float_flag:
            self.textfield = \
                DoubleTextfield(lower, upper, step_size, default).textfield
        else:
            self.textfield = \
                IntegerTextfield(lower, upper, step_size, default).textfield

        # Label
        self.label = QLabel()
        self.label.setText(name + ": ")

        # Connect Textfield with Slider
        def textfield_value_changed(value):
            self.slider.setValue(to_internal_coordinate(value))

        def slider_value_changed(value):
            self.textfield.setValue(to_external_coordinate(value))

        self.textfield.valueChanged.connect(textfield_value_changed)
        self.slider.valueChanged.connect(slider_value_changed)

        self.SingleSlidersLayout = QBoxLayout(QBoxLayout.LeftToRight)
        self.SingleSlidersLayout.addWidget(self.label)
        self.SingleSlidersLayout.addWidget(self.slider)
        self.SingleSlidersLayout.addWidget(self.textfield)
        self.setLayout(self.SingleSlidersLayout)
        self.setFixedHeight(70)
        self.setFlat(True)

        self.textfield.valueChanged.connect(lambda : slot(self.textfield.value()))
Пример #9
0
def stylePyqtProperty( type, origName ):
	name = '_' + origName

	signalName = origName + 'Changed' 
	signal = pyqtSignal( type, name = signalName )

	def getter( self ):
		return self.lookupProperty( origName )

	def setter( self, value ):
		print( f'setting {origName} of {self.parent().metaObject().className()} to {value}' )
		if getattr( self, name ) != value:
			setattr( self, name, value )
			self.notify( origName, value )

	return pyqtProperty( type, getter, setter, notify = signal ), signal
Пример #10
0
def dataPyqtProperty( type, origName ):
	name = '_' + origName

	signalName = origName + 'Changed' 
	signal = pyqtSignal( type, name = signalName )

	def getter( self ):
		return getattr( self, name )

	def setter( self, value ):
		if value != getter( self ):
			getattr( self, signalName ).emit( value )

		setattr( self, name, value )

	return pyqtProperty( type, getter, setter, notify = signal ), signal
    def __init__(self, name, default):
        super(CheckBoxWidget, self).__init__()
        self.valueChanged = pyqtSignal()

        # CheckBox itself
        self.checkbox = PyQt5.QtWidgets.QCheckBox()
        self.checkbox.setEnabled(default)

        # Label
        self.label = PyQt5.QtWidgets.QLabel()
        self.label.setText(name + ": ")

        self.SingleCheckBoxLayout = PyQt5.QtWidgets.QGridLayout()
        self.SingleCheckBoxLayout.setAlignment(Qt.AlignLeft)
        self.SingleCheckBoxLayout.addWidget(self.label, 0, 0)
        self.SingleCheckBoxLayout.addWidget(self.checkbox, 0, 1)
        self.setLayout(self.SingleCheckBoxLayout)
    def __init__(self, name, options):
        super(ComboBoxWidget, self).__init__()
        self.valueChanged = pyqtSignal()

        # ComboBox itself
        self.combobox = QtWidgets.QComboBox()
        self.combobox.orientationCombo = PyQt5.QtWidgets.QComboBox()
        self.combobox.orientationCombo.addItems(options)

        # Label
        self.label = PyQt5.QtWidgets.QLabel()
        self.label.setText(name + ": ")

        self.SingleCheckBoxLayout = PyQt5.QtWidgets.QGridLayout()
        self.SingleCheckBoxLayout.setAlignment(Qt.AlignLeft)
        self.SingleCheckBoxLayout.addWidget(self.label, 0, 0)
        self.SingleCheckBoxLayout.addWidget(self.combobox, 0, 1)
        self.setLayout(self.SingleCheckBoxLayout)
    def __init__(self, name):
        super(ComboBoxWidget, self).__init__()
        self.valueChanged = pyqtSignal()

        # ComboBox itself
        self.combobox = QtWidgets.QComboBox()
        self.combobox.orientationCombo = PyQt5.QtWidgets.QComboBox()
        self.combobox.setFixedWidth(220)

        # Label
        self.label = PyQt5.QtWidgets.QLabel()
        self.label.setText(name + ": ")

        self.SingleCheckBoxLayout = QBoxLayout(QBoxLayout.LeftToRight)
        self.SingleCheckBoxLayout.addWidget(self.label)
        self.SingleCheckBoxLayout.addWidget(self.combobox, Qt.AlignRight)
        self.setLayout(self.SingleCheckBoxLayout)
        self.setFixedHeight(50)
        self.setFlat(True)
    def __init__(self, name, default, slot):
        super(CheckBoxWidget, self).__init__()
        self.stateChanged = pyqtSignal()

        # CheckBox itself
        self.checkbox = PyQt5.QtWidgets.QCheckBox()
        self.checkbox.setChecked(default)

        # Label
        self.label = PyQt5.QtWidgets.QLabel()
        self.label.setText(name + ": ")

        self.SingleCheckBoxLayout = PyQt5.QtWidgets.QGridLayout()
        self.SingleCheckBoxLayout.setAlignment(Qt.AlignLeft)
        self.SingleCheckBoxLayout.addWidget(self.label, 0, 0)
        self.SingleCheckBoxLayout.addWidget(self.checkbox, 0, 1)
        self.setLayout(self.SingleCheckBoxLayout)
        self.setFixedHeight(70)
        self.setFlat(True)

        self.checkbox.stateChanged.connect(slot)
Пример #15
0
        def handleDict(dct, setattr):
            # don't touch attributes other than magicProperties
            properties = list(
                filter(
                    lambda i: isinstance(i[1], magicProperty),
                    dct.items()
                )
            )
            for property_name, p in properties:
                signal = pyqtSignal(p.property_type)
                signal_name = p.signal or property_name + "_changed"

                # create dedicated signal for each property
                setattr(signal_name, signal)

                # substitute magicProperty placeholder with real pyqtProperty
                setattr(property_name, magicWrapperType.magic_property(
                    property_name,
                    p.property_type,
                    signal,
                    signal_name
                ))
Пример #16
0
class UploadWindow(QMainWindow):
    progress_update_signal = pyqtSignal(str)

    def __init__(self,
                 uploader,
                 config_file=None,
                 credential_file=None,
                 hostname=None,
                 window_title=None,
                 cookie_persistence=True):
        super(UploadWindow, self).__init__()
        qApp.aboutToQuit.connect(self.quitEvent)

        assert uploader is not None
        self.uploader = None
        self.auth_window = None
        self.identity = None
        self.current_path = None
        self.uploading = False
        self.save_progress_on_cancel = False

        self.ui = UploadWindowUI(self)
        self.ui.title = window_title if window_title else "Deriva Upload Utility %s" % __version__
        self.setWindowTitle(self.ui.title)

        self.config_file = config_file
        self.credential_file = credential_file
        self.cookie_persistence = cookie_persistence

        self.show()
        self.configure(uploader, hostname)

    def configure(self, uploader, hostname):
        # if a hostname has been provided, it overrides whatever default host a given uploader is configured for
        server = None
        if hostname:
            server = dict()
            if hostname.startswith("http"):
                url = urllib.parse.urlparse(hostname)
                server["protocol"] = url.scheme
                server["host"] = url.netloc
            else:
                server["protocol"] = "https"
                server["host"] = hostname

        # instantiate the uploader...
        # if an uploader instance does not have a default host configured, prompt the user to configure one
        if self.uploader:
            del self.uploader
        self.uploader = uploader(self.config_file, self.credential_file, server, dcctx_cid="gui/DerivaUploadGUI")
        if not self.uploader.server:
            if not self.checkValidServer():
                return
            else:
                self.uploader.setServer(server)

        self.setWindowTitle("%s (%s)" % (self.ui.title, self.uploader.server["host"]))

        self.getNewAuthWindow()
        if not self.checkVersion():
            return
        self.getSession()

    def getNewAuthWindow(self):
        if self.auth_window:
            if self.auth_window.authenticated():
                self.on_actionLogout_triggered()
            self.auth_window.deleteLater()

        self.auth_window = \
            EmbeddedAuthWindow(self,
                               config=self.uploader.server,
                               cookie_persistence=
                               self.uploader.server.get("cookie_persistence", self.cookie_persistence),
                               authentication_success_callback=self.onLoginSuccess,
                               log_level=logging.getLogger().getEffectiveLevel())
        self.ui.actionLogin.setEnabled(True)

    def getSession(self):
        qApp.setOverrideCursor(Qt.WaitCursor)
        logging.debug("Validating session: %s" % self.uploader.server["host"])
        queryTask = SessionQueryTask(self.uploader)
        queryTask.status_update_signal.connect(self.onSessionResult)
        queryTask.query()

    def onLoginSuccess(self, **kwargs):
        self.auth_window.hide()
        self.uploader.setCredentials(kwargs["credential"])
        self.getSession()

    def enableControls(self):
        self.ui.actionUpload.setEnabled(self.canUpload())
        self.ui.actionRescan.setEnabled(self.current_path is not None and self.auth_window.authenticated())
        self.ui.actionCancel.setEnabled(False)
        self.ui.actionOptions.setEnabled(True)
        self.ui.actionLogin.setEnabled(not self.auth_window.authenticated())
        self.ui.actionLogout.setEnabled(self.auth_window.authenticated())
        self.ui.actionExit.setEnabled(True)
        self.ui.browseButton.setEnabled(True)

    def disableControls(self):
        self.ui.actionUpload.setEnabled(False)
        self.ui.actionRescan.setEnabled(False)
        self.ui.actionOptions.setEnabled(False)
        self.ui.actionLogin.setEnabled(False)
        self.ui.actionLogout.setEnabled(False)
        self.ui.actionExit.setEnabled(False)
        self.ui.browseButton.setEnabled(False)

    def closeEvent(self, event=None):
        self.disableControls()
        if self.uploading:
            self.cancelTasks(self.cancelConfirmation())
        if event:
            event.accept()

    def checkValidServer(self):
        self.restoreCursor()
        if self.uploader.server and self.uploader.server.get("host"):
            return True
        msg = QMessageBox()
        msg.setIcon(QMessageBox.Warning)
        msg.setWindowTitle("No Server Configured")
        msg.setText("Add server configuration now?")
        msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
        ret = msg.exec_()
        if ret == QMessageBox.Yes:
            self.on_actionOptions_triggered()
        else:
            return False

    def checkAllowSessionCaching(self):
        client_settings = self.uploader.config.get("client_settings")
        if not client_settings:
            return
        allow_session_caching = stob(client_settings.get("allow_session_caching", True))
        cookie_persistence = self.uploader.server.get("cookie_persistence", False)
        if cookie_persistence != allow_session_caching:
            if not allow_session_caching:
                self.uploader.server["cookie_persistence"] = False
                servers = list()
                for server in self.uploader.getServers():
                    if server.get("host", "") != self.uploader.server.get("host"):
                        servers.append(server)
                servers.append(self.uploader.server)
                setServers = getattr(self.uploader, "setServers", None)
                if callable(setServers):
                    setServers(servers)

    def onServerChanged(self, server):
        if server is None or server == self.uploader.server:
            return

        qApp.setOverrideCursor(Qt.WaitCursor)
        self.uploader.setServer(server)
        self.restoreCursor()
        if not self.checkValidServer():
            return
        self.setWindowTitle("%s (%s)" % (self.ui.title, self.uploader.server["host"]))
        self.getNewAuthWindow()
        self.getSession()

    def cancelTasks(self, save_progress):
        qApp.setOverrideCursor(Qt.WaitCursor)
        self.save_progress_on_cancel = save_progress
        self.uploader.cancel()
        Task.shutdown_all()
        self.statusBar().showMessage("Waiting for background tasks to terminate...")

        while True:
            qApp.processEvents()
            if QThreadPool.globalInstance().waitForDone(10):
                break

        self.uploading = False
        self.statusBar().showMessage("All background tasks terminated successfully")
        self.restoreCursor()

    def uploadCallback(self, **kwargs):
        completed = kwargs.get("completed")
        total = kwargs.get("total")
        file_path = kwargs.get("file_path")
        file_name = os.path.basename(file_path) if file_path else ""
        job_info = kwargs.get("job_info", {})
        job_info.update()
        if completed and total:
            file_name = " [%s]" % file_name
            job_info.update({"completed": completed, "total": total, "host": kwargs.get("host")})
            status = "Uploading file%s: %d%% complete" % (file_name, round(((completed / total) % 100) * 100))
            self.uploader.setTransferState(file_path, job_info)
        else:
            summary = kwargs.get("summary", "")
            file_name = "Uploaded file: [%s] " % file_name
            status = file_name  # + summary
        if status:
            self.progress_update_signal.emit(status)

        if self.uploader.cancelled:
            if self.save_progress_on_cancel:
                return -1
            else:
                return False

        return True

    def restoreCursor(self):
        qApp.restoreOverrideCursor()
        qApp.processEvents()

    def statusCallback(self, **kwargs):
        status = kwargs.get("status")
        self.progress_update_signal.emit(status)

    def displayUploads(self, upload_list):
        keys = ["State",
                "Status",
                "File"]
        hidden = ["State"]
        self.ui.uploadList.setRowCount(len(upload_list))
        self.ui.uploadList.setColumnCount(len(keys))

        rows = 0
        for row in upload_list:
            cols = 0
            for key in keys:
                item = QTableWidgetItem()
                value = row.get(key)
                text = str(value) or ""
                item.setText(text)
                item.setToolTip("<span>" + text + "</span>")
                self.ui.uploadList.setItem(rows, cols, item)
                if key in hidden:
                    self.ui.uploadList.hideColumn(cols)
                cols += 1
            rows += 1

        self.ui.uploadList.setHorizontalHeaderLabels(keys)  # add header names
        self.ui.uploadList.horizontalHeader().setDefaultAlignment(Qt.AlignLeft)  # set alignment
        self.ui.uploadList.resizeColumnToContents(0)

    def canUpload(self):
        return (self.ui.uploadList.rowCount() > 0) and self.auth_window.authenticated()

    def checkVersion(self):
        if not self.uploader.isVersionCompatible():
            self.updateStatus("Version incompatibility detected", "Current version: %s, required version: %s" % (
                self.uploader.getVersion(), self.uploader.getVersionCompatibility()))
            self.disableControls()
            self.ui.actionExit.setEnabled(True)
            self.ui.actionOptions.setEnabled(True)
            self.updateConfirmation()
            return False

        self.checkAllowSessionCaching()
        self.resetUI("Ready...")
        return True

    def updateConfirmation(self):
        url = self.uploader.config.get("version_update_url")
        if not url:
            return
        self.restoreCursor()
        msg = QMessageBox()
        msg.setIcon(QMessageBox.Warning)
        msg.setWindowTitle("Version incompatibility detected!")
        msg.setText("Current version: %s\nRequired version: %s\n\nLaunch browser and download required version?"
                    % (self.uploader.getVersion(), self.uploader.getVersionCompatibility()))
        msg.setInformativeText(
            "Selecting \"Yes\" will close the application and launch an external web browser which will take you to a "
            "download page where you can get the required version of this software.")
        msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
        ret = msg.exec_()
        if ret == QMessageBox.Yes:
            webbrowser.open_new(url)
            self.deleteLater()

    def updateConfig(self):
        qApp.setOverrideCursor(Qt.WaitCursor)
        configUpdateTask = ConfigUpdateTask(self.uploader)
        configUpdateTask.status_update_signal.connect(self.onUpdateConfigResult)
        configUpdateTask.update_config()

    def scanDirectory(self, reset=False):
        self.uploader.reset()
        scanTask = ScanDirectoryTask(self.uploader)
        scanTask.status_update_signal.connect(self.onScanResult)
        scanTask.scan(self.current_path)

    @pyqtSlot(str)
    def updateProgress(self, status):
        if status:
            self.statusBar().showMessage(status)
        else:
            self.displayUploads(self.uploader.getFileStatusAsArray())

    @pyqtSlot(str, str)
    def updateStatus(self, status, detail=None, success=True):
        msg = status + ((": %s" % detail) if detail else "")
        logging.info(msg) if success else logging.error(msg)
        self.statusBar().showMessage(status)

    @pyqtSlot(str, str)
    def resetUI(self, status, detail=None, success=True):
        self.updateStatus(status, detail, success)
        self.enableControls()

    @pyqtSlot(str)
    def updateLog(self, text):
        self.ui.logTextBrowser.widget.appendPlainText(text)

    @pyqtSlot(bool, str, str, object)
    def onSessionResult(self, success, status, detail, result):
        self.restoreCursor()
        if success:
            self.identity = result["client"]["id"]
            display_name = result["client"]["full_name"]
            self.setWindowTitle("%s (%s - %s)" % (self.ui.title, self.uploader.server["host"], display_name))
            self.ui.actionLogout.setEnabled(True)
            self.ui.actionLogin.setEnabled(False)
            if self.current_path:
                self.ui.actionRescan.setEnabled(True)
                self.ui.actionUpload.setEnabled(True)
            self.updateStatus("Logged in.")
            self.updateConfig()
        else:
            self.updateStatus("Login required.")

    @pyqtSlot(bool, str, str, object)
    def onUpdateConfigResult(self, success, status, detail, result):
        self.restoreCursor()
        if not success:
            self.resetUI(status, detail)
            return
        if not result:
            return
        confirm_updates = stob(self.uploader.server.get("confirm_updates", False))
        if confirm_updates:
            msg = QMessageBox()
            msg.setIcon(QMessageBox.Information)
            msg.setWindowTitle("Updated Configuration Available")
            msg.setText("Apply updated configuration?")
            msg.setInformativeText(
                "Selecting \"Yes\" will apply the latest configuration from the server and overwrite the existing "
                "default configuration file.\n\nSelecting \"No\" will ignore these updates and continue to use the "
                "existing configuration.\n\nYou should always apply the latest configuration changes from the server "
                "unless you understand the risk involved with using a potentially out-of-date configuration.")

            msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
            ret = msg.exec_()
            if ret == QMessageBox.No:
                return

        write_config(self.uploader.getDeployedConfigFilePath(), result)
        self.uploader.initialize(cleanup=False)
        if not self.checkVersion():
            return
        self.on_actionRescan_triggered()

    @pyqtSlot()
    def on_actionBrowse_triggered(self):
        dialog = QFileDialog()
        path = dialog.getExistingDirectory(self,
                                           "Select Directory",
                                           self.current_path,
                                           QFileDialog.ShowDirsOnly)
        if not path:
            return
        self.current_path = path
        self.ui.pathTextBox.setText(os.path.normpath(self.current_path))
        self.scanDirectory()

    @pyqtSlot()
    def on_actionRescan_triggered(self):
        if not self.current_path:
            return

        self.scanDirectory()

    @pyqtSlot(bool, str, str, object)
    def onScanResult(self, success, status, detail, result):
        self.restoreCursor()
        if success:
            self.displayUploads(self.uploader.getFileStatusAsArray())
            self.ui.actionUpload.setEnabled(self.canUpload())
            self.resetUI("Ready...")
            if self.uploading:
                self.on_actionUpload_triggered()
        else:
            self.resetUI(status, detail, success)

    @pyqtSlot()
    def on_actionUpload_triggered(self):
        if not self.uploading:
            if self.uploader.cancelled:
                self.uploading = True
                self.on_actionRescan_triggered()
                return

        self.disableControls()
        self.ui.actionCancel.setEnabled(True)
        self.save_progress_on_cancel = False
        qApp.setOverrideCursor(Qt.WaitCursor)
        self.uploading = True
        self.updateStatus("Uploading...")
        self.progress_update_signal.connect(self.updateProgress)
        uploadTask = UploadFilesTask(self.uploader)
        uploadTask.status_update_signal.connect(self.onUploadResult)
        uploadTask.upload(status_callback=self.statusCallback, file_callback=self.uploadCallback)

    @pyqtSlot(bool, str, str, object)
    def onUploadResult(self, success, status, detail, result):
        self.restoreCursor()
        self.uploading = False
        self.displayUploads(self.uploader.getFileStatusAsArray())
        if success:
            self.resetUI("Ready.")
        else:
            self.resetUI(status, detail, success)

    @pyqtSlot()
    def on_actionCancel_triggered(self):
        self.cancelTasks(self.cancelConfirmation())
        self.restoreCursor()
        self.displayUploads(self.uploader.getFileStatusAsArray())
        self.resetUI("Ready.")

    @pyqtSlot()
    def on_actionLogin_triggered(self):
        if not self.auth_window:
            if self.checkValidServer():
                self.getNewAuthWindow()
            else:
                return
        self.auth_window.show()
        self.auth_window.login()

    @pyqtSlot()
    def on_actionLogout_triggered(self):
        self.setWindowTitle("%s (%s)" % (self.ui.title, self.uploader.server["host"]))
        self.auth_window.logout()
        self.identity = None
        self.ui.actionUpload.setEnabled(False)
        self.ui.actionRescan.setEnabled(False)
        self.ui.actionLogout.setEnabled(False)
        self.ui.actionLogin.setEnabled(True)
        self.updateStatus("Logged out.")

    @pyqtSlot()
    def on_actionOptions_triggered(self):
        OptionsDialog.getOptions(self)

    @pyqtSlot()
    def on_actionHelp_triggered(self):
        pass

    @pyqtSlot()
    def on_actionExit_triggered(self):
        self.closeEvent()
        qApp.quit()

    def quitEvent(self):
        if self.auth_window:
            self.auth_window.logout(self.logoutConfirmation())
        qApp.closeAllWindows()
        self.deleteLater()

    def logoutConfirmation(self):
        if self.auth_window and (not self.auth_window.authenticated() or not self.auth_window.cookie_persistence):
            return
        msg = QMessageBox()
        msg.setIcon(QMessageBox.Warning)
        msg.setWindowTitle("Confirm Action")
        msg.setText("Do you wish to completely logout of the system?")
        msg.setInformativeText("Selecting \"Yes\" will clear the login state and invalidate the current user identity."
                               "\n\nSelecting \"No\" will keep your current identity cached, which will allow you to "
                               "log back in without authenticating until your session expires.\n\nNOTE: Select \"Yes\" "
                               "if this is a shared system using a single user account.")
        msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
        ret = msg.exec_()
        if ret == QMessageBox.Yes:
            return True
        return False

    def cancelConfirmation(self):
        self.restoreCursor()
        msg = QMessageBox()
        msg.setIcon(QMessageBox.Warning)
        msg.setWindowTitle("Confirm Action")
        msg.setText("Save progress for the current upload?")
        msg.setInformativeText("Selecting \"Yes\" will attempt to resume this transfer from the point where it was "
                               "cancelled.\n\nSelecting \"No\" will require the transfer to be started over from the "
                               "beginning of file.")
        msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
        ret = msg.exec_()
        if ret == QMessageBox.Yes:
            return True
        return False
Пример #17
0
class PluginRegistry(QObject):
    APIVersion = 4

    def __init__(self, parent=None):
        super().__init__(parent)
        self._plugins = {}  # type: Dict[str, types.ModuleType]
        self._plugin_objects = {}  # type: Dict[str, PluginObject]
        self._meta_data = {}  # type: Dict[str, Dict[str, any]]
        self._plugin_locations = []  # type: List[str]
        self._folder_cache = {}  # type: Dict[str, str]
        self._application = None
        self._active_plugins = []  # type: List[str]

        self._supported_file_types = {"umplugin": "Uranium Plugin"}
        preferences = Preferences.getInstance()
        preferences.addPreference("general/disabled_plugins", "")
        # The disabled_plugins is explicitly set to None. When actually loading the preferences, it's set to a list.
        # This way we can see the difference between no list and an empty one.
        self._disabled_plugins = None  # type: Optional[List[str]]

    def addSupportedPluginExtension(self, extension, description):
        if extension not in self._supported_file_types:
            self._supported_file_types[extension] = description
            self.supportedPluginExtensionsChanged.emit()

    supportedPluginExtensionsChanged = pyqtSignal()

    @pyqtProperty("QStringList", notify=supportedPluginExtensionsChanged)
    def supportedPluginExtensions(self):
        file_types = []
        all_types = []

        if Platform.isLinux():
            for ext, desc in self._supported_file_types.items():
                file_types.append("{0} (*.{1} *.{2})".format(
                    desc, ext.lower(), ext.upper()))
                all_types.append("*.{0} *.{1}".format(ext.lower(),
                                                      ext.upper()))
        else:
            for ext, desc in self._supported_file_types.items():
                file_types.append("{0} (*.{1})".format(desc, ext))
                all_types.append("*.{0}".format(ext))

        file_types.sort()
        file_types.insert(
            0,
            i18n_catalog.i18nc("@item:inlistbox", "All Supported Types ({0})",
                               " ".join(all_types)))
        file_types.append(
            i18n_catalog.i18nc("@item:inlistbox", "All Files (*)"))
        return file_types

    @pyqtSlot(str, result=bool)
    def isPluginFile(self, plugin_path: str):
        extension = os.path.splitext(plugin_path)[1].strip(".")
        if extension.lower() in self._supported_file_types.keys():
            return True
        return False

    @pyqtSlot(str, result="QVariantMap")
    def uninstallPlugin(self, plugin_id: str):
        Logger.log("d", "Uninstall plugin got ID: %s", plugin_id)
        plugin_folder = os.path.join(
            Resources.getStoragePath(Resources.Resources), "plugins")
        plugin_path = os.path.join(plugin_folder, plugin_id)
        Logger.log("i", "Attempting to uninstall %s", plugin_path)
        result = {"status": "error", "message": "", "id": plugin_id}
        success_message = i18n_catalog.i18nc(
            "@info:status",
            "The plugin has been removed.\nPlease re-start the application to finish uninstall."
        )

        try:
            shutil.rmtree(plugin_path)
        except:
            Logger.logException("d",
                                "An exception occurred while uninstalling %s",
                                plugin_path)

            result["message"] = i18n_catalog.i18nc(
                "@info:status", "Failed to uninstall plugin")
            return result

        result["status"] = "ok"
        result["message"] = success_message
        return result

    @pyqtSlot(str, result="QVariantMap")
    def installPlugin(self, plugin_path: str):
        Logger.log("d", "Install plugin got path: %s", plugin_path)
        plugin_path = QUrl(plugin_path).toLocalFile()
        Logger.log("i", "Attempting to install a new plugin %s", plugin_path)
        local_plugin_path = os.path.join(
            Resources.getStoragePath(Resources.Resources), "plugins")
        plugin_folder = ""
        result = {"status": "error", "message": "", "id": ""}
        success_message = i18n_catalog.i18nc(
            "@info:status",
            "The plugin has been installed.\nPlease re-start the application to activate the plugin."
        )

        try:
            with zipfile.ZipFile(plugin_path, "r") as zip_ref:
                plugin_id = None
                for file in zip_ref.infolist():
                    if file.filename.endswith("/"):
                        plugin_id = file.filename.strip("/")
                        break

                if plugin_id is None:
                    result["message"] = i18n_catalog.i18nc(
                        "@info:status",
                        "Failed to install plugin from <filename>{0}</filename>:\n<message>{1}</message>",
                        plugin_path, "Invalid plugin archive.")
                    return result
                result["id"] = plugin_id
                plugin_folder = os.path.join(local_plugin_path, plugin_id)

                if os.path.isdir(
                        plugin_folder
                ):  # Plugin is already installed by user (so not a bundled plugin)
                    metadata = {}
                    with zip_ref.open(plugin_id +
                                      "/plugin.json") as metadata_file:
                        metadata = json.loads(
                            metadata_file.read().decode("utf-8"))

                    if "version" in metadata:
                        new_version = Version(metadata["version"])
                        old_version = Version(
                            self.getMetaData(plugin_id)["plugin"]["version"])
                        if new_version > old_version:
                            for info in zip_ref.infolist():
                                extracted_path = zip_ref.extract(
                                    info.filename, path=plugin_folder)
                                permissions = os.stat(extracted_path).st_mode
                                os.chmod(extracted_path,
                                         permissions | stat.S_IEXEC
                                         )  #Make these files executable.
                            result["status"] = "ok"
                            result["message"] = success_message
                            return result

                    Logger.log(
                        "w",
                        "The plugin was already installed. Unable to install it again!"
                    )
                    result["status"] = "duplicate"
                    result["message"] = i18n_catalog.i18nc(
                        "@info:status",
                        "Failed to install the plugin;\n<message>{0}</message>",
                        "Plugin was already installed")
                    return result
                elif plugin_id in self._plugins:
                    # Plugin is already installed, but not by the user (eg; this is a bundled plugin)
                    # TODO: Right now we don't support upgrading bundled plugins at all, but we might do so in the future.
                    result["message"] = i18n_catalog.i18nc(
                        "@info:status",
                        "Failed to install the plugin;\n<message>{0}</message>",
                        "Unable to upgrade or install bundled plugins.")
                    return result

                for info in zip_ref.infolist():
                    extracted_path = zip_ref.extract(info.filename,
                                                     path=plugin_folder)
                    permissions = os.stat(extracted_path).st_mode
                    os.chmod(extracted_path, permissions
                             | stat.S_IEXEC)  #Make these files executable.

        except:  # Installing a new plugin should never crash the application.
            Logger.logException(
                "d",
                "An exception occurred while installing plugin {path}".format(
                    path=plugin_path))

            result["message"] = i18n_catalog.i18nc(
                "@info:status",
                "Failed to install plugin from <filename>{0}</filename>:\n<message>{1}</message>",
                plugin_folder, "Invalid plugin file")
            return result

        result["status"] = "ok"
        result["message"] = success_message
        return result

    ##  Check if all required plugins are loaded.
    #   \param required_plugins \type{list} List of ids of plugins that ''must'' be activated.
    def checkRequiredPlugins(self, required_plugins: List[str]) -> bool:
        plugins = self._findAllPlugins()
        for plugin_id in required_plugins:
            if plugin_id not in plugins:
                Logger.log("e",
                           "Plugin %s is required, but not added or loaded",
                           plugin_id)
                return False
        return True

    ##  Get the list of active plugins.
    def getActivePlugins(self) -> List[str]:
        return self._active_plugins

    ##  Ask whether plugin_name is an active plugin.
    #
    #   \param plugin_id \type{string} The id of the plugin which might be active or not.
    def isActivePlugin(self, plugin_id: str) -> bool:
        return plugin_id in self._active_plugins

    ##  Remove plugin from the list of active plugins.
    #
    #   \param plugin_id \type{string} The id of the plugin to remove.
    def removeActivePlugin(self, plugin_id: str):
        if plugin_id in self._active_plugins:
            self._active_plugins.remove(plugin_id)
        if plugin_id not in self._disabled_plugins:
            self._disabled_plugins.append(plugin_id)
            Preferences.getInstance().setValue(
                "general/disabled_plugins", ",".join(self._disabled_plugins))

    ##  Add a plugin to the list of active plugins.
    #
    #   \param plugin_id \type{string} The id of the plugin to add.
    def addActivePlugin(self, plugin_id: str):
        if plugin_id not in self._active_plugins:
            self._active_plugins.append(plugin_id)
        if plugin_id in self._disabled_plugins:
            self._disabled_plugins.remove(plugin_id)
            Preferences.getInstance().setValue(
                "general/disabled_plugins", ",".join(self._disabled_plugins))

    ##  Load a single plugin by id
    #   \param plugin_id \type{string} The ID of the plugin, i.e. its directory name.
    #   \exception PluginNotFoundError Raised when the plugin could not be found.
    def loadPlugin(self, plugin_id: str):
        if plugin_id in self._plugins:
            # Already loaded, do not load again
            Logger.log("w", "Plugin %s was already loaded", plugin_id)
            return

        if not self._disabled_plugins:
            self._disabled_plugins = Preferences.getInstance().getValue(
                "general/disabled_plugins").split(",")
        if plugin_id in self._disabled_plugins:
            Logger.log("d", "Plugin %s was disabled", plugin_id)
            return

        plugin = self._findPlugin(plugin_id)
        if not plugin:
            raise PluginNotFoundError(plugin_id)

        if plugin_id not in self._meta_data:
            try:
                self._populateMetaData(plugin_id)
            except InvalidMetaDataError:
                return

        if self._meta_data[plugin_id].get("plugin", {}).get(
                "api", 0) != self.APIVersion:
            Logger.log("i",
                       "Plugin %s uses an incompatible API version, ignoring",
                       plugin_id)
            return

        try:
            to_register = plugin.register(self._application)
            if not to_register:
                Logger.log("e",
                           "Plugin %s did not return any objects to register",
                           plugin_id)
                return
            for plugin_type, plugin_object in to_register.items():
                if type(plugin_object) == list:
                    for nested_plugin_object in plugin_object:
                        self._addPluginObject(nested_plugin_object, plugin_id,
                                              plugin_type)
                else:
                    self._addPluginObject(plugin_object, plugin_id,
                                          plugin_type)

            self._plugins[plugin_id] = plugin
            self.addActivePlugin(plugin_id)
            Logger.log("i", "Loaded plugin %s", plugin_id)

        except KeyError as e:
            Logger.log("e", "Error loading plugin %s:", plugin_id)
            Logger.log("e", "Unknown plugin type: %s", str(e))
        except Exception as e:
            Logger.logException("e", "Error loading plugin %s:", plugin_id)

    def _addPluginObject(self, plugin_object: PluginObject, plugin_id: str,
                         plugin_type: str):
        plugin_object.setPluginId(plugin_id)
        self._plugin_objects[plugin_id] = plugin_object
        try:
            self._type_register_map[plugin_type](plugin_object)
        except Exception as e:
            Logger.logException("e", "Unable to add plugin %s", plugin_id)

    ##  Load all plugins matching a certain set of metadata
    #   \param meta_data \type{dict} The meta data that needs to be matched.
    #   \sa loadPlugin
    def loadPlugins(self, meta_data: Optional[dict] = None):
        plugins = self._findAllPlugins()

        for plugin_id in plugins:
            plugin_data = self.getMetaData(plugin_id)
            if meta_data is None or self._subsetInDict(plugin_data, meta_data):
                try:
                    self.loadPlugin(plugin_id)
                except PluginNotFoundError:
                    pass

    ##  Get a plugin object
    #   \param plugin_id \type{string} The ID of the plugin object to get.
    def getPluginObject(self, plugin_id: str) -> PluginObject:
        if plugin_id not in self._plugins:
            self.loadPlugin(plugin_id)
        return self._plugin_objects[plugin_id]

    ##  Get the metadata for a certain plugin
    #   \param plugin_id \type{string} The ID of the plugin
    #   \return \type{dict} The metadata of the plugin. Can be an empty dict.
    #   \exception InvalidMetaDataError Raised when no metadata can be found or the metadata misses the right keys.
    def getMetaData(self, plugin_id: str) -> Dict:
        if plugin_id not in self._meta_data:
            try:
                if not self._populateMetaData(plugin_id):
                    return {}
            except InvalidMetaDataError:
                return {}

        return self._meta_data[plugin_id]

    ##  Get the path to a plugin.
    #
    #   \param plugin_id \type{string} The ID of the plugin.
    #   \return \type{string} The absolute path to the plugin or an empty string if the plugin could not be found.
    def getPluginPath(self, plugin_id: str) -> Optional[str]:
        if plugin_id in self._plugins:
            plugin = self._plugins[plugin_id]
        else:
            plugin = self._findPlugin(plugin_id)

        if not plugin:
            return None

        path = os.path.dirname(self._plugins[plugin_id].__file__)
        if os.path.isdir(path):
            return path

        return None

    ##  Get a list of all metadata matching a certain subset of metadata
    #   \param kwargs Keyword arguments.
    #                 Possible keywords:
    #                 - filter: \type{dict} The subset of metadata that should be matched.
    #                 - active_only: Boolean, True when only active plugin metadata should be returned.
    #   \sa getMetaData
    def getAllMetaData(self, **kwargs) -> List:
        data_filter = kwargs.get("filter", {})
        active_only = kwargs.get("active_only", False)

        plugins = self._findAllPlugins()
        return_values = []
        for plugin_id in plugins:
            if active_only and plugin_id not in self._active_plugins:
                continue

            plugin_data = self.getMetaData(plugin_id)
            if self._subsetInDict(plugin_data, data_filter):
                return_values.append(plugin_data)

        return return_values

    ##  Get the list of plugin locations
    #   \return \type{list} The plugin locations
    def getPluginLocations(self) -> List:
        return self._plugin_locations

    ##  Add a plugin location to the list of locations to search
    #   \param location \type{string} The location to add to the list
    def addPluginLocation(self, location: str):
        #TODO: Add error checking!
        self._plugin_locations.append(location)

    ##  Set the central application object
    #   This is used by plugins as a central access point for other objects
    #   \param app \type{Application} The application object to use
    def setApplication(self, app):
        self._application = app

    ##  Add a new plugin type.
    #
    #   This function is used to add new plugin types. Plugin types are simple
    #   string identifiers that match a certain plugin to a registration function.
    #
    #   The callable `register_function` is responsible for handling the object.
    #   Usually it will add the object to a list of objects in the relevant class.
    #   For example, the plugin type 'tool' has Controller::addTool as register
    #   function.
    #
    #   `register_function` will be called every time a plugin of `type` is loaded.
    #
    #   \param type \type{string} The name of the plugin type to add.
    #   \param register_function \type{callable} A callable that takes an object as parameter.
    @classmethod
    def addType(cls, plugin_type: str, register_function: Callable[[Any],
                                                                   None]):
        cls._type_register_map[plugin_type] = register_function

    ##  Remove a plugin type.
    #
    #   \param type \type{string} The plugin type to remove.
    @classmethod
    def removeType(cls, plugin_type: str):
        if plugin_type in cls._type_register_map:
            del cls._type_register_map[plugin_type]

    ##  Get the singleton instance of this class.
    ##  \return instance \type{PluginRegistry}
    @classmethod
    def getInstance(cls) -> "PluginRegistry":
        if not cls._instance:
            cls._instance = PluginRegistry()
        return cls._instance

    ##  private:
    #   Populate the list of metadata
    #   \param plugin_id \type{string}
    #   \return
    def _populateMetaData(self, plugin_id: str) -> bool:
        plugin = self._findPlugin(plugin_id)
        if not plugin:
            Logger.log("w", "Could not find plugin %s", plugin_id)
            return False

        meta_data = None

        location = None
        for folder in self._plugin_locations:
            location = self._locatePlugin(plugin_id, folder)
            if location:
                break

        if not location:
            Logger.log("w", "Could not find plugin %s", plugin_id)
            return False
        location = os.path.join(location, plugin_id)

        try:
            meta_data = plugin.getMetaData()

            metadata_file = os.path.join(location, "plugin.json")
            try:
                with open(metadata_file, "r") as f:
                    try:
                        meta_data["plugin"] = json.loads(f.read())
                    except json.decoder.JSONDecodeError:
                        Logger.logException(
                            "e", "Failed to parse plugin.json for plugin %s",
                            plugin_id)
                        raise InvalidMetaDataError(plugin_id)

                    # Check if metadata is valid;
                    if "version" not in meta_data["plugin"]:
                        Logger.log("e", "Version must be set!")
                        raise InvalidMetaDataError(plugin_id)

                    if "i18n-catalog" in meta_data["plugin"]:
                        # A catalog was set, try to translate a few strings
                        i18n_catalog = i18nCatalog(
                            meta_data["plugin"]["i18n-catalog"])
                        if "name" in meta_data["plugin"]:
                            meta_data["plugin"]["name"] = i18n_catalog.i18n(
                                meta_data["plugin"]["name"])
                        if "description" in meta_data["plugin"]:
                            meta_data["plugin"][
                                "description"] = i18n_catalog.i18n(
                                    meta_data["plugin"]["description"])

            except FileNotFoundError:
                Logger.logException(
                    "e",
                    "Unable to find the required plugin.json file for plugin %s",
                    plugin_id)
                raise InvalidMetaDataError(plugin_id)

        except AttributeError as e:
            Logger.log(
                "e", "An error occurred getting metadata from plugin %s: %s",
                plugin_id, str(e))
            raise InvalidMetaDataError(plugin_id)

        if not meta_data:
            raise InvalidMetaDataError(plugin_id)

        meta_data["id"] = plugin_id
        meta_data["location"] = location

        # Application-specific overrides
        appname = self._application.getApplicationName()
        if appname in meta_data:
            meta_data.update(meta_data[appname])
            del meta_data[appname]

        self._meta_data[plugin_id] = meta_data
        return True

    ##   Try to find a module implementing a plugin
    #   \param plugin_id \type{string} The name of the plugin to find
    #   \returns module \type{module} if it was found None otherwise
    def _findPlugin(self, plugin_id: str) -> types.ModuleType:
        location = None
        for folder in self._plugin_locations:
            location = self._locatePlugin(plugin_id, folder)
            if location:
                break

        if not location:
            return None

        try:
            file, path, desc = imp.find_module(plugin_id, [location])
        except Exception:
            Logger.logException("e", "Import error when importing %s",
                                plugin_id)
            return None

        try:
            module = imp.load_module(plugin_id, file, path, desc)
        except Exception:
            Logger.logException("e", "Import error loading module %s",
                                plugin_id)
            return None
        finally:
            if file:
                os.close(file)

        return module

    #   Returns a list of all possible plugin ids in the plugin locations
    def _findAllPlugins(self, paths=None):
        ids = []

        if not paths:
            paths = self._plugin_locations

        for folder in paths:
            if not os.path.isdir(folder):
                continue

            for file in os.listdir(folder):
                filepath = os.path.join(folder, file)
                if os.path.isdir(filepath):
                    if os.path.isfile(os.path.join(filepath, "__init__.py")):
                        ids.append(file)
                    else:
                        ids += self._findAllPlugins([filepath])

        return ids

    #   Try to find a directory we can use to load a plugin from
    #   \param plugin_id \type{string} The id of the plugin to locate
    #   \param folder The base folder to look into
    def _locatePlugin(self, plugin_id: str, folder: str) -> Optional[str]:
        if not os.path.isdir(folder):
            return None

        if folder not in self._folder_cache:
            sub_folders = []
            for file in os.listdir(folder):
                file_path = os.path.join(folder, file)
                if os.path.isdir(file_path):
                    entry = (file, file_path)
                    sub_folders.append(entry)
            self._folder_cache[folder] = sub_folders

        for (file, file_path) in self._folder_cache[folder]:
            if file == plugin_id and os.path.exists(
                    os.path.join(file_path, "__init__.py")):
                return folder
            else:
                file_path = self._locatePlugin(plugin_id, file_path)
                if file_path:
                    return file_path

        return None

    #   Check if a certain dictionary contains a certain subset of key/value pairs
    #   \param dictionary \type{dict} The dictionary to search
    #   \param subset \type{dict} The subset to search for
    def _subsetInDict(self, dictionary: Dict, subset: Dict) -> bool:
        for key in subset:
            if key not in dictionary:
                return False
            if subset[key] != {} and dictionary[key] != subset[key]:
                return False
        return True

    _type_register_map = {}  # type: Dict[str, Callable[[Any], None]]
    _instance = None  # type: PluginRegistry
Пример #18
0
class DataFrameWidget(QTableView):

    cellClicked = pyqtSignal(int, int)

    def __init__(self, parent=None, df=None):
        """ DataFrame widget

        first argument needs to be parent because QtDesigner
        passes the parent QWidget when instantiating the object

        """
        super(DataFrameWidget, self).__init__(parent)

        self._data_model = DataFrameModel()
        self.setModel(self._data_model)

        if df is None:
            df = pandas.DataFrame()

        self.setDataFrame(df)

        # create (horizontal/top) header menu bindings
        self.horizontalHeader().setContextMenuPolicy(Qt.CustomContextMenu)
        self.horizontalHeader().customContextMenuRequested.connect(
            self._header_menu)

        # create (vertical/side/row) header menu bindings
        self.verticalHeader().setContextMenuPolicy(Qt.CustomContextMenu)
        self.verticalHeader().customContextMenuRequested.connect(
            self._index_menu)

        # create custom QTableView menu bindings
        self.setContextMenuPolicy(Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self._table_menu)

        # we intercept the clicked slot and connect to our own _on_click
        # to emit a custom signal
        self.clicked.connect(self._on_click)

    def keyPressEvent(self, event):
        # intercept "Copy" and "Paste" key combinations or delete
        if event.matches(QKeySequence.Copy):
            self.copy()
        elif event.matches(QKeySequence.Paste):
            # load clipboard content as dataframe
            self.paste()
        elif event.matches(QKeySequence.Delete):
            self.clear()
        else:
            # ignore the event, pass through normal event handler
            super(DataFrameWidget, self).keyPressEvent(event)

    def setDataFrame(self, dataframe):
        self._data_model.resetFilter()
        self._data_model.resetExcluded()
        self._data_model.setDataFrame(dataframe)
        self.resizeColumnsToContents()

    def _on_click(self, index):
        if index.isValid():
            self.cellClicked.emit(index.row(), index.column())

    def copy(self):
        """ copy selected cells into clipboard """
        selindexes = self.selectedIndexes()

        if len(selindexes) < 1:
            # nothing is selected
            return None

        # create a DataFrame container to hold data
        container = pandas.DataFrame()

        for sel in selindexes:
            row = sel.row()
            col = sel.column()
            entry = sel.data()
            if entry:
                container.at[row, col] = entry
            else:
                # if cell empty, fill with nan
                container.at[row, col] = nan

        str2copy = "\n".join(
            ["\t".join([f"{r}" for r in row]) for row in container.values])

        QApplication.clipboard().setText(str2copy)

    def paste(self):
        """ paste data copied on clipboard """

        selindexes = self.selectedIndexes()

        if len(selindexes) != 1:
            alert(
                title="Alert",
                message="To paste into table, select a single cell",
            )
        else:
            clipboard = QApplication.clipboard().text()

            if clipboard is None or clipboard == "":
                return

            dfnew = pandas.read_csv(StringIO(clipboard), sep="\t", header=None)
            Nrows, Ncols = dfnew.shape
            # current position
            row_id, col_id = selindexes[0].row(), selindexes[0].column()
            # figure out if the current size of the table fits
            rowsize, colsize = self._data_model.df.shape

            Ncol_extra = (0 if col_id + Ncols <= colsize else col_id + Ncols -
                          colsize)
            Nrow_extra = (0 if row_id + Nrows <= rowsize else row_id + Nrows -
                          rowsize)

            if Ncol_extra > 0:
                self._data_model.insertColumns(colsize, count=Ncol_extra)
            if Nrow_extra > 0:
                self._data_model.insertRows(rowsize, count=Nrow_extra)

            self._data_model.layoutAboutToBeChanged.emit()
            self._data_model.df.iloc[row_id:row_id + Nrows,
                                     col_id:col_id + Ncols] = dfnew.values
            self._data_model.layoutChanged.emit()

            self.resizeColumnsToContents()

    def clear(self):
        """ clear selected cell contents """
        selindexes = self.selectedIndexes()

        self._data_model.layoutAboutToBeChanged.emit()

        for sel in selindexes:
            row = sel.row()
            col = sel.column()

            self._data_model.df.iloc[row, col] = nan

        self._data_model.layoutChanged.emit()

    def _header_menu(self, pos):
        """ context menu for header cells """
        menu = QMenu(self)
        column_index = self.horizontalHeader().logicalIndexAt(pos)

        if column_index == -1:
            # out of bounds
            return

        menu.addAction(r"Rename column",
                       partial(self.renameHeader, column_index))
        menu.addSeparator()

        menu.addAction(r"Copy selected header", self.copyHeader)
        menu.addSeparator()
        menu.addAction(
            r"Sort (Descending)",
            partial(self._data_model.sort,
                    column_index,
                    order=Qt.DescendingOrder),
        )

        menu.addAction(
            r"Sort (Ascending)",
            partial(self._data_model.sort,
                    column_index,
                    order=Qt.AscendingOrder),
        )
        menu.addSeparator()
        menu.addAction(
            r"Insert column <-",
            partial(self._data_model.insertColumns, column_index),
        )

        menu.addAction(
            r"Insert column ->",
            partial(self._data_model.insertColumns, column_index + 1),
        )

        menu.addAction(r"Delete selected column(s)",
                       self.removeSelectedColumns)

        menu.exec_(self.mapToGlobal(pos))

    def _index_menu(self, pos):
        """ context menu for index/row cells """
        menu = QMenu(self)
        row_index = self.verticalHeader().logicalIndexAt(pos)

        if row_index == -1:
            # out of bounds
            return

        menu.addAction(r"Insert row above",
                       partial(self._data_model.insertRows, row_index))

        menu.addAction(
            r"Insert row below",
            partial(self._data_model.insertRows, row_index + 1),
        )

        menu.addAction(r"Delete selected row(s)", self.removeSelectedRows)

        menu.exec_(self.mapToGlobal(pos))

    def _table_menu(self, pos):
        menu = QMenu(self)
        menu.addAction(r"Copy selected", self.copy)
        menu.addAction(r"Paste", self.paste)
        menu.addAction(r"Clear contents", self.clear)
        menu.addSeparator()
        menu.addAction(r"Exclude selected row(s)", self.exclude_selected_rows)
        menu.addSeparator()
        menu.addAction(r"Remove exclusion", self.remove_exlusion)
        menu.addSeparator()
        menu.addAction(r"Set filter", self.prototype_set_filter)
        menu.exec_(self.mapToGlobal(pos))

    def copyHeader(self):

        selindexes = self.selectedIndexes()
        selcolumns = sorted(
            list(set([sel.column() for sel in self.selectedIndexes()])))

        if len(selindexes) < 1:
            # nothing is selected
            return None

        # create a DataFrame container to hold data
        headernames = []

        for col in selcolumns:
            headernames.append(self._data_model.df.columns[col])

        str2copy = "\t".join(headernames)
        QApplication.clipboard().setText(str2copy)

    def renameHeader(self, column_index):

        newname, ok = QInputDialog.getText(self, "Rename column header",
                                           "New column name:")

        newname = newname.strip()

        # name can't be empty
        if newname != "":
            newcolumns = self._data_model.df.columns.tolist()
            newcolumns[column_index] = newname
            self._data_model.df.columns = newcolumns

    def removeSelectedRows(self):
        # get the number of rows
        Nrows = self._data_model.df.shape[0]

        if Nrows > 1:
            selected_rows = sorted(
                list(set([sel.row() for sel in self.selectedIndexes()])))

            while len(selected_rows) > 0:
                target = selected_rows.pop()
                self._data_model.removeRows(target)
        else:
            alert("Warning", "Can't remove the only remaining row.")

    def removeSelectedColumns(self):
        Ncolumns = self._data_model.df.shape[1]

        if Ncolumns > 1:
            selected_columns = sorted(
                list(set([sel.column() for sel in self.selectedIndexes()])))

            while len(selected_columns) > 0:
                target = selected_columns.pop()
                self._data_model.removeColumns(target)
        else:
            alert("Warning", "Can't remove the only remaining column.")

    def exclude_selected_rows(self):
        selected_rows = list(
            set([
                self._data_model.df.index[sel.row()]
                for sel in self.selectedIndexes()
            ]))

        for row in selected_rows:
            self._data_model.excluded_index.append(row)

    def remove_exlusion(self):
        selected_rows = list(
            set([
                self._data_model.df.index[sel.row()]
                for sel in self.selectedIndexes()
            ]))

        for row in selected_rows:
            if row in self._data_model.excluded_index:
                self._data_model.excluded_index.remove(row)

    def prototype_set_filter(self):
        column_names = self._data_model.df.columns
        # check if table still has excluded data
        Nexcluded = len(self._data_model.excluded_index)
        if Nexcluded > 0:
            alert("Warning",
                  "please remove excluded data before doing filtering.")
        else:
            filter_dialog = FilterDialog(column_names, parent=self)
            filter_dialog.show()
            filter_dialog.exec_()

    def getVisibleData(self):

        data = self._data_model.df
        excluded = self._data_model.excluded_index

        return data.drop(excluded, axis=0)
Пример #19
0
class TableView(QTableView):

    deleteKeyPress = pyqtSignal(QModelIndex, str, str)
    replaceSignal = pyqtSignal(QModelIndex, str, str)

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

        self.setShowGrid(False)
        self.setAlternatingRowColors(True)
        # self.setDragEnabled(True)
        # self.setDropIndicatorShown(True)
        # self.setDragDropOverwriteMode(False)
        # self.setAcceptDrops(True)
        # self.viewport().setAcceptDrops(True)

        # self.setDragDropMode(QAbstractItemView.InternalMove)

        # self.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.setSelectionMode(QAbstractItemView.ExtendedSelection)
        # self.verticalHeader().setMinimumWidth(40)
        # self.verticalHeader().setSectionResizeMode(QHeaderView.Stretch)
        # QHeaderView.setsize
        self.setEditTriggers(QAbstractItemView.DoubleClicked)
        self.matches = deque()
        self.undoStack = QUndoStack()
        self.dialog = SearchAndReplace(self)
        self.dialog.nextBotton.clicked.connect(self.nextMatch)
        self.dialog.caseSensitive.stateChanged.connect(self.resetMatches)
        self.dialog.regxp.stateChanged.connect(self.resetMatches)
        self.dialog.textToSearch.textChanged.connect(self.resetMatches)
        self.dialog.repalceBotton.clicked.connect(self.replace)
        self.dialog.repalceAllButton.clicked.connect(self.replaceAll)
        self.dialog.textToSearch.textChanged.connect(self.resetMatches)

        self.current_col = 0
        self.incr = 0

    @property
    def currentRow(self):
        return self.currentIndex().row()

    def resetMatches(self):
        self.matches.clear()
        self.current_col = 0

    def setUndoStack(self, stack: QUndoStack):
        self.undoStack = stack

    def searchTable(self, text):
        if self.dialog.isVisible():
            self.dialog.beginToSearch()
            model = self.model()
            if self.dialog.regxp.checkState() == Qt.Checked:
                option = Qt.MatchExactly
            else:
                option = Qt.MatchContains
            if self.dialog.caseSensitive.checkState() == Qt.Checked:
                option |= Qt.MatchCaseSensitive

            for col in range(model.columnCount()):
                matches = model.findItems(
                    text,
                    option,
                    column=col,
                )
                self.matches.extend(matches)
            if not self.matches:
                # self.dialog.msg.setHidden(False)
                self.dialog.noMatchFound()
                return False
        return True

    def focusInEvent(self, *args, **kwargs):
        if not self.selectedIndexes():
            self.selectRow(0)
        super(TableView, self).focusInEvent(*args, **kwargs)

    def getPlaceHolder(self):
        incr_pattern = re.compile(r'{([0-9]+)}')
        textToReplace = self.dialog.textToReplace.text()
        match = incr_pattern.search(textToReplace)
        return match

    def nextMatch(self, pop=False):
        if not self.dialog.isVisible():
            return
        if not self.matches:
            textToSearch = self.dialog.textToSearch.text()
            found = self.searchTable(textToSearch)
            if not found:
                return
            match = self.getPlaceHolder()
            if match:
                self.incr = int(match.group(1).strip())
        else:
            if self.dialog.go_down.checkState() == Qt.Unchecked:
                self.matches.rotate(1)
            else:
                if not pop:
                    self.matches.rotate(-1)
        current_item = self.matches[0]
        index = self.model().indexFromItem(current_item)
        if index.column() != self.current_col:
            self.current_col = index.column()
            match = self.getPlaceHolder()
            if match:
                self.incr = int(match.group(1).strip())
        self.selectionModel().clearSelection()
        self.setCurrentIndex(index)
        self.raise_()
        self.activateWindow()

    def replace(self):
        if not self.dialog.isVisible():
            return
        if not self.matches:
            self.nextMatch()
            return
        textToSearch = self.dialog.textToSearch.text()
        textToReplace = self.dialog.textToReplace.text()
        # incr_pattern = re.compile(r'{([0-9]+)}')
        match = self.getPlaceHolder()
        if match:
            textToReplace = textToReplace.replace(match.group(0),
                                                  str(self.incr))

        index = self.currentIndex()
        model = self.model()
        item = model.itemFromIndex(index)
        old = item.text()
        if self.dialog.caseSensitive.checkState() == Qt.Checked:
            pattern = re.compile(textToSearch)
        else:
            pattern = re.compile(textToSearch, re.IGNORECASE)
        new = pattern.sub(textToReplace, old)
        self.replaceSignal.emit(index, new, old)
        self.matches.popleft()
        self.incr += 1
        if not self.matches:
            # self.dialog.noMatchFound()
            self.dialog.raise_()
            self.dialog.activateWindow()
            return
        else:
            self.nextMatch(pop=True)

    def replaceAll(self):
        if not self.matches:
            self.nextMatch()
        while True:
            if not self.matches:
                break
            self.replace()

    def keyPressEvent(self, e: QKeyEvent):
        # table state
        # https://doc.qt.io/archives/qt-4.8/qabstractitemview.html#State-enum
        state = self.state()
        key = e.key()
        index = self.currentIndex()
        if self.dialog.isVisible():
            if key == Qt.Key_N:
                self.nextMatch()
                return
            elif key == Qt.Key_R:
                self.replace()
                return

        if state == QAbstractItemView.EditingState:
            if e.modifiers() == Qt.ControlModifier:
                if key in key_remap:
                    self.commitData(self.focusWidget())
                    self.closeEditor(self.focusWidget(),
                                     QAbstractItemDelegate.NoHint)
                    e = QKeyEvent(QEvent.KeyPress, key_remap.get(key),
                                  Qt.NoModifier, e.text())
                    super(TableView, self).keyPressEvent(e)
                    self.edit(self.currentIndex())
                    return

        if state == QAbstractItemView.NoEditTriggers:
            if key in (Qt.Key_I, Qt.Key_S):
                self.edit(index)
                return

            if key in key_remap.keys():
                e = QKeyEvent(QEvent.KeyPress, key_remap.get(key),
                              e.modifiers(), e.text())

            if key == Qt.Key_G:  # press shift+g go to bottom of row
                if e.modifiers() == Qt.ShiftModifier:
                    row_cnt = self.model().rowCount() - 1
                    sibling = index.sibling(row_cnt, index.column())

                else:  # press g go to top of row
                    sibling = index.sibling(0, index.column())
                self.setCurrentIndex(sibling)
                return
            if key == Qt.Key_Delete or key == Qt.Key_D:
                self.deleteKeyPress.emit(self.currentIndex(), '', '')
                return
        super(TableView, self).keyPressEvent(e)
Пример #20
0
# Importing from  PyQt5.QtWidgets
from PyQt5.QtWidgets import QWidget
from PyQt5.QtWidgets import QTextEdit
from PyQt5.QtWidgets import QStyledItemDelegate
from PyQt5.QtWidgets import QUndoStack
from PyQt5.QtWidgets import QUndoCommand
from PyQt5.QtWidgets import QComboBox
from PyQt5.QtWidgets import QSpinBox
from PyQt5.QtWidgets import QDoubleSpinBox
from PyQt5.QtWidgets import QLineEdit

from .undoCommands import CommandElementChange
import tableWidget.GRO_parse as GRO_parse
from .residColors import *  # REsidue Color  RGB values

comitDataSignal = pyqtSignal(QWidget, name="commitData")
closeEditorSignal = pyqtSignal(QWidget, name="closeEditor")

try:
    from PyQt5.QtCore import QString
except ImportError:
    # we are using Python3 so QString is not defined
    QString = str

(residNum, residName, atomName, atomNum, X, Y, Z) = range(7)

MAGIC_NUMBER = 0x570C4
FILE_VERSION = 1


class GRO_rowInfo(object):
Пример #21
0
class Document(QObject):
    """Manages opening and saving the document, modified status, etc"""

    # region --- Class constants ---

    # File Open dialog extension filters for file types we can read
    OPEN_FILTERS = [
        'All files',
        # 'All supported raster formats (*.bmp *.gif *.jpg *.jpeg *.png)',
        # 'BMP - Microsoft Windows Device Independent Bitmap (*.bmp)',
        # 'GIF - Graphics Interchange Format (*.gif)',
        # 'JPEG - Joint Photographic Experts Group JFIF (*.jpg *.jpeg)',
        # 'PNG - Portable Network Graphics (*.png)',
        # -----------------
        # 'All supported audio formats (*.flac *.mp3 *.ogg *.wav)',
        # 'AIFF - Audio Interchange File Format (*.aif, *.aiff, *.aifc)',
        # 'AU/SND - Sun Microsystems Audio File Format (*.au, *.snd)',
        # 'FLAC - Free Lossless Audio Codec (*.flac)',
        # 'MP3 - MPEG Layer 3 (*.mp3)',
        # 'OGG - Ogg Vorbis (*.ogg)',
        'Wave Audio File Format (*.wav)'
    ]

    # Default File Open dialog extension filter (list index)
    DFLT_OPEN_FILTER = -1

    # File Save As dialog extension filters for file types we can write
    SAVE_FILTERS = [
        'All files',
        # 'All supported raster formats (*.bmp *.gif *.jpg *.jpeg *.png)',
        # 'BMP - Microsoft Windows Device Independent Bitmap (*.bmp)',
        # 'GIF - Graphics Interchange Format (*.gif)',
        # 'JPEG - Joint Photographic Experts Group JFIF (*.jpg *.jpeg)',
        # 'PNG - Portable Network Graphics (*.png)',
        # -----------------
        # 'All supported audio formats (*.flac *.mp3 *.ogg *.wav)',
        # 'AIFF - Audio Interchange File Format (*.aif, *.aiff, *.aifc)',
        # 'AU/SND - Sun Microsystems Audio File Format (*.au, *.snd)',
        # 'FLAC - Free Lossless Audio Codec (*.flac)',
        # 'MP3 - MPEG Layer 3 (*.mp3)',
        # 'OGG - Ogg Vorbis (*.ogg)',
        'Wave Audio File Format (*.wav)'
    ]

    # Default File Save As dialog extension filter (list index)
    DFLT_SAVE_FILTER = -1

    # Mask for rw-rw-rw- permissions
    PERMISSION_MASK = ~(QFile.ReadOwner | QFile.WriteOwner | QFile.ReadGroup |
                        QFile.WriteGroup | QFile.ReadOther | QFile.WriteOther)

    # Application name suffix (used in window captions)
    UNTITLED_DOC_NAME = 'Untitled'

    # endregion

    # region --- Signals ---

    # The document contents have changed
    contents_changed = pyqtSignal()

    # A document property has changed; parameters are: bool old_value, bool new_value
    modified_changed = pyqtSignal(bool, bool)

    # A document property has changed; parameters are: str old_name, str new_name
    filename_changed = pyqtSignal(str, str)

    # endregion

    # region --- Initialization ---

    def __init__(self):
        super(Document, self).__init__()

        self.bytes_per_sample = Settings.DOC_BYTES_PER_SAMPLE_DFLT
        self.channels = Settings.DOC_CHANNELS_DFLT
        self.sampling_rate = Settings.DOC_SAMPLING_RATE_DFLT
        self.frames = Settings.DOC_FRAMES_DFLT

        self.contents = bytes(self.frames * self.bytes_per_sample *
                              self.channels)

        self._filename = None
        self._is_modified = False

    # endregion

    # region --- Properties ---

    @property
    def filename(self):
        """Get or set the full path of the document.
        """
        return self._filename

    # This is a function vs property because it has associated logic but isn't public
    def _set_filename(self, new_value):
        if self._filename != new_value:
            old_value = self._filename
            self._filename = new_value
            if new_value:
                # Update our default open/save directory
                Settings.set_cwd(QFileInfo(new_value).dir().canonicalPath())
            self.filename_changed.emit(old_value, new_value)

    @property
    def is_modified(self):
        """Get or set the is_modified flag. Changing is_modified will emit a modified_changed signal."""
        return self._is_modified

    @is_modified.setter
    def is_modified(self, new_value):
        if self._is_modified != new_value:
            old_value = self._is_modified
            self._is_modified = new_value
            self.modified_changed.emit(old_value, new_value)

    @property
    def title(self):
        """Get or set the current filename. Setting the filename to a new value will update the title automatically,
        and emit a filename_changed signal.
        """
        return ellipsize(
            self.filename) if self.filename else Document.UNTITLED_DOC_NAME

    # endregion

    # region --- Commands ---

    def new(self, parent):
        # If current file has been modified, offer to save
        if self.close(parent) is False:
            return False

        # Allow the user to specify properties of the new document
        dlg = DlgNew(parent)
        if dlg.exec_() is False:
            return False

        self.bytes_per_sample = dlg.bytes_per_sample
        self.channels = dlg.channels
        self.sampling_rate = dlg.sampling_rate
        self.frames = dlg.frames

        self.contents = bytes(self.frames * self.bytes_per_sample *
                              self.channels)

        self._set_filename(None)
        self.is_modified = False

        return True

    def open(self, parent, filename=None):
        """Opens the specified file and reads the contents into memory, setting filename and is_modified
        appropriately.
        :type parent: QWidget - The parent window for dialogs, alerts, etc.
        :type filename: str - Full path of file or None (the default)
        :rtype: bool - True = Successfully opened file, False = error or cancelled (user was notified)
        """
        # If no filename was passed in, prompt the user
        if filename is None:
            filename, _ = QFileDialog.getOpenFileName(
                parent, make_caption('Open File'), Settings.get_cwd(),
                ";;".join(Document.OPEN_FILTERS),
                Document.OPEN_FILTERS[Document.DFLT_OPEN_FILTER])
            if not filename:
                return False

        # If current file has been modified, offer to save
        if self.close(parent) is False:
            return False

        # Read in the WAV file
        try:
            with wave.open(filename, mode='rb') as wave_read:
                # Get the audio file properties
                self.bytes_per_sample = wave_read.getsampwidth()
                self.channels = wave_read.getnchannels()
                self.sampling_rate = wave_read.getframerate()
                self.frames = wave_read.getnframes()

                # Read the content into memory
                # TODO - Read entire file into memory (need to enable chunking eventually)
                self.contents = wave_read.readframes(self.frames)

        except wave.Error as e:
            # An error occurred, notify user and return False
            QMessageBox.critical(
                parent, make_caption('Warning'),
                "Unable to open file:\n\n\t<b>{}</b>\n\n\n{}".format(
                    filename, e.args[0]))
            return False

        # except IOError as e:   # TODO - test this; can we detect file_not_found and read_permission_error?

        self._set_filename(filename)
        self.contents_changed.emit()

        return True

    def save(self, parent, filename=None):
        """Saves the current file and sets is_modified to False.
        If the file has never been saved (i.e., doesn't have a filename) or is not writable, this method calls save_as.
        :type parent: QWidget - The parent window for dialogs, alerts, etc.
        :type filename: str - Full path of file or None (the default)
        :rtype: bool - True = Successfully saved file, False = error (user was notified) or user cancelled
        """
        if filename is None:
            filename = self.filename

        # If the current file has never been saved...
        if filename is None:
            # ...call save_as instead
            self.on_file_saveas()

        wavfile = QFile(filename)
        # If the original file exists, but can't be opened for writing...
        if wavfile.exists() and not wavfile.open(QIODevice.ReadWrite):
            # ...then treat this as a Save-As command
            return self.save_as(parent)

        tmpfile = QTemporaryFile(filename)
        # If we can't open our temporary file for writing...
        if not QTemporaryFile.open(QIODevice.WriteOnly):
            # ...something's wrong (disk full?), so report it to the user and exit
            QMessageBox.critical(
                parent, make_caption('Warning'),
                "Unable to create temporary file:\n\n\t<b>{}</b>\n\n\n{}".
                format(tmpfile.fileName(), tmpfile.errorString()))
            return False

        # If the original file exists...
        if wavfile.exists():
            # ...set the temporary file permissions to match the original
            tmpfile.setPermissions(wavfile.permissions())  # Ignore errors
        elif sys.platform == 'win32':
            # ...otherwise (on Windows) use standard permissions
            tmpfile.setPermissions(QFile.WriteGroup | QFile.WriteOther)
        elif sys.platform.startswith('linux') or sys.platform.startswith(
                'darwin'):
            # ...otherwise (on Linux) use the file mode creation mask for the current process
            tmpfile.setPermissions(self.umask_as_permission()
                                   & Document.PERMISSION_MASK)
        else:
            raise RuntimeError(
                'Unsupported platform: ' +
                sys.platform)  # TODO - check this at startup, not here

        tmpfile.close()  # TODO - is this necessary?

        # Write out the WAV file
        try:
            with wave.open(tmpfile.fileName(), mode='wb') as wave_write:
                # Set the audio file properties
                wave_write.setnchannels(self.channels)
                wave_write.setsampwidth(self.bytes_per_sample)
                wave_write.setframerate(self.sampling_rate)
                wave_write.setnframes(self.frames)
                wave_write.setcomptype(None)

                # Write the contents of memory out to the file
                wave_write.writeframes(self.frames)

        except wave.Error as e:
            # An error occurred, notify user and return False
            QMessageBox.critical(
                parent, make_caption('Warning'),
                "Unable to write file:\n\n\t<b>{}</b>\n\n\n{}".format(
                    tmpfile.fileName(), e.args[0]))
            return False

        # Data was written successfully to temp file, so inform QTemporaryFile not to remove it automatically; we're
        # going to rename it to the original file
        tmpfile.setAutoRemove(False)

        backup_filename = filename + '~'
        # Remove any previous backup
        QFile.remove(backup_filename)
        # Rename the original file to the backup name
        QFile.rename(filename, backup_filename)
        # Rename the temporary file to the original name
        if not tmpfile.rename(filename):
            # If anything goes wrong, rename the backup file back to the original name
            QFile.rename(backup_filename, filename)
            QMessageBox.critical(
                parent, make_caption('Warning'),
                "Unable to rename file:\n\n\tFrom: <b>{}</b>\n\tTo: <b>{}</b>\n\n\n{}"
                .format(backup_filename, filename, tmpfile.errorString()))
            return False

        settings = QSettings()
        if not settings.value(
                Settings.CREATE_BACKUP_ON_SAVE,
                defaultValue=Settings.CREATE_BACKUP_ON_SAVE_DFLT):
            QFile.remove(backup_filename)

        self.is_modified = False
        self._set_filename(filename)

        return True

    def save_as(self, parent):
        """Prompts the user for a filename, saves the current file, and sets is_modified to False.
        :type parent: QWidget - The parent window for dialogs, alerts, etc.
        :rtype: bool - True = Successfully saved file, False = error (user was notified) or user cancelled
        """
        # Prompt for a save file name. If the file already exists, QFileDialog will prompt for overwrite permission.
        filename, _ = QFileDialog.getSaveFileName(
            parent, make_caption('Save File As'), Settings.get_cwd(),
            ";;".join(Document.SAVE_FILTERS),
            Document.SAVE_FILTERS[Document.DFLT_SAVE_FILTER])

        # If user canceled, just return False
        if not filename:
            return False

        return self.save(parent, filename)

    def close(self, parent):
        """Prompts the user to save if there are unsaved changes, then closes the file and sets is_modified to False.
        :type parent: QWidget - The parent window for dialogs, alerts, etc.
        :rtype: bool - True = Successfully closed file, False = error (user was notified) or user cancelled
        """
        # If current file has been modified, offer to save
        if self.is_modified:
            response = QMessageBox.warning(
                parent, make_caption('Warning'),
                'Save changes to {} before closing?'.format(self.title),
                QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel,
                QMessageBox.Save)
            # If user cancels, return False
            if response == QMessageBox.Cancel:
                return False
            # IF use attempts to save, and it either fails or the user cancels, return False
            elif response == QMessageBox.Save and self.save(self) is False:
                return False

        self.bytes_per_sample = Settings.DOC_BYTES_PER_SAMPLE_DFLT
        self.channels = Settings.DOC_CHANNELS_DFLT
        self.sampling_rate = Settings.DOC_SAMPLING_RATE_DFLT
        self.frames = Settings.DOC_FRAMES_DFLT

        self.contents = bytes(self.frames * self.bytes_per_sample *
                              self.channels)

        self._set_filename(None)
        self.is_modified = False

        return True

    def undo(self):
        # Search for QUndoStack
        # and https://doc.qt.io/archives/qtjambi-4.5.2_01/com/trolltech/qt/qtjambi-undoframework.html
        pass

    def redo(self):
        pass

    # endregion

    # region --- Helpers ---

    @staticmethod
    def umask_as_permission():
        # Get the file mode creation mask for the current process
        umask = os.umask(0)

        # Restore the original mask (note that this isn't thread-safe)
        os.umask(umask)

        # Convert it into a combination of Qt Permission flags
        return (0 if not S_IRUSR(umask) else QFile.ReadOwner) | \
               (0 if not S_IWUSR(umask) else QFile.WriteOwner) | \
               (0 if not S_IXUSR(umask) else QFile.ExeOwner) | \
               (0 if not S_IRGRP(umask) else QFile.ReadGroup) | \
               (0 if not S_IWGRP(umask) else QFile.WriteGroup) | \
               (0 if not S_IXGRP(umask) else QFile.ExeGroup) | \
               (0 if not S_IROTH(umask) else QFile.ReadOther) | \
               (0 if not S_IWOTH(umask) else QFile.WriteOther) | \
               (0 if not S_IXOTH(umask) else QFile.ExeOther)
Пример #22
0
class PrinterOutputModel(QObject):
    bedTemperatureChanged = pyqtSignal()
    targetBedTemperatureChanged = pyqtSignal()
    isPreheatingChanged = pyqtSignal()
    stateChanged = pyqtSignal()
    activePrintJobChanged = pyqtSignal()
    nameChanged = pyqtSignal()
    headPositionChanged = pyqtSignal()
    keyChanged = pyqtSignal()
    typeChanged = pyqtSignal()
    buildplateChanged = pyqtSignal()
    cameraUrlChanged = pyqtSignal()
    configurationChanged = pyqtSignal()
    canUpdateFirmwareChanged = pyqtSignal()

    def __init__(self,
                 output_controller: "PrinterOutputController",
                 number_of_extruders: int = 1,
                 parent=None,
                 firmware_version="") -> None:
        super().__init__(parent)
        self._bed_temperature = -1  # type: float  # Use -1 for no heated bed.
        self._target_bed_temperature = 0  # type: float
        self._name = ""
        self._key = ""  # Unique identifier
        self._controller = output_controller
        self._controller.canUpdateFirmwareChanged.connect(
            self._onControllerCanUpdateFirmwareChanged)
        self._extruders = [
            ExtruderOutputModel(printer=self, position=i)
            for i in range(number_of_extruders)
        ]
        self._printer_configuration = PrinterConfigurationModel(
        )  # Indicates the current configuration setup in this printer
        self._head_position = Vector(0, 0, 0)
        self._active_print_job = None  # type: Optional[PrintJobOutputModel]
        self._firmware_version = firmware_version
        self._printer_state = "unknown"
        self._is_preheating = False
        self._printer_type = ""
        self._buildplate = ""

        self._printer_configuration.extruderConfigurations = [
            extruder.extruderConfiguration for extruder in self._extruders
        ]

        self._camera_url = QUrl()  # type: QUrl

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

    def setCameraUrl(self, camera_url: "QUrl") -> None:
        if self._camera_url != camera_url:
            self._camera_url = camera_url
            self.cameraUrlChanged.emit()

    @pyqtProperty(QUrl, fset=setCameraUrl, notify=cameraUrlChanged)
    def cameraUrl(self) -> "QUrl":
        return self._camera_url

    def updateIsPreheating(self, pre_heating: bool) -> None:
        if self._is_preheating != pre_heating:
            self._is_preheating = pre_heating
            self.isPreheatingChanged.emit()

    @pyqtProperty(bool, notify=isPreheatingChanged)
    def isPreheating(self) -> bool:
        return self._is_preheating

    @pyqtProperty(str, notify=typeChanged)
    def type(self) -> str:
        return self._printer_type

    def updateType(self, printer_type: str) -> None:
        if self._printer_type != printer_type:
            self._printer_type = printer_type
            self._printer_configuration.printerType = self._printer_type
            self.typeChanged.emit()
            self.configurationChanged.emit()

    @pyqtProperty(str, notify=buildplateChanged)
    def buildplate(self) -> str:
        return self._buildplate

    def updateBuildplate(self, buildplate: str) -> None:
        if self._buildplate != buildplate:
            self._buildplate = buildplate
            self._printer_configuration.buildplateConfiguration = self._buildplate
            self.buildplateChanged.emit()
            self.configurationChanged.emit()

    @pyqtProperty(str, notify=keyChanged)
    def key(self) -> str:
        return self._key

    def updateKey(self, key: str) -> None:
        if self._key != key:
            self._key = key
            self.keyChanged.emit()

    @pyqtSlot()
    def homeHead(self) -> None:
        self._controller.homeHead(self)

    @pyqtSlot()
    def homeBed(self) -> None:
        self._controller.homeBed(self)

    @pyqtSlot(str)
    def sendRawCommand(self, command: str) -> None:
        self._controller.sendRawCommand(self, command)

    @pyqtProperty("QVariantList", constant=True)
    def extruders(self) -> List["ExtruderOutputModel"]:
        return self._extruders

    @pyqtProperty(QVariant, notify=headPositionChanged)
    def headPosition(self) -> Dict[str, float]:
        return {
            "x": self._head_position.x,
            "y": self._head_position.y,
            "z": self.head_position.z
        }

    def updateHeadPosition(self, x: float, y: float, z: float) -> None:
        if self._head_position.x != x or self._head_position.y != y or self._head_position.z != z:
            self._head_position = Vector(x, y, z)
            self.headPositionChanged.emit()

    @pyqtProperty(float, float, float)
    @pyqtProperty(float, float, float, float)
    def setHeadPosition(self,
                        x: float,
                        y: float,
                        z: float,
                        speed: float = 3000) -> None:
        self.updateHeadPosition(x, y, z)
        self._controller.setHeadPosition(self, x, y, z, speed)

    @pyqtProperty(float)
    @pyqtProperty(float, float)
    def setHeadX(self, x: float, speed: float = 3000) -> None:
        self.updateHeadPosition(x, self._head_position.y,
                                self._head_position.z)
        self._controller.setHeadPosition(self, x, self._head_position.y,
                                         self._head_position.z, speed)

    @pyqtProperty(float)
    @pyqtProperty(float, float)
    def setHeadY(self, y: float, speed: float = 3000) -> None:
        self.updateHeadPosition(self._head_position.x, y,
                                self._head_position.z)
        self._controller.setHeadPosition(self, self._head_position.x, y,
                                         self._head_position.z, speed)

    @pyqtProperty(float)
    @pyqtProperty(float, float)
    def setHeadZ(self, z: float, speed: float = 3000) -> None:
        self.updateHeadPosition(self._head_position.x, self._head_position.y,
                                z)
        self._controller.setHeadPosition(self, self._head_position.x,
                                         self._head_position.y, z, speed)

    @pyqtSlot(float, float, float)
    @pyqtSlot(float, float, float, float)
    def moveHead(self,
                 x: float = 0,
                 y: float = 0,
                 z: float = 0,
                 speed: float = 3000) -> None:
        self._controller.moveHead(self, x, y, z, speed)

    ##  Pre-heats the heated bed of the printer.
    #
    #   \param temperature The temperature to heat the bed to, in degrees
    #   Celsius.
    #   \param duration How long the bed should stay warm, in seconds.
    @pyqtSlot(float, float)
    def preheatBed(self, temperature: float, duration: float) -> None:
        self._controller.preheatBed(self, temperature, duration)

    @pyqtSlot()
    def cancelPreheatBed(self) -> None:
        self._controller.cancelPreheatBed(self)

    def getController(self) -> "PrinterOutputController":
        return self._controller

    @pyqtProperty(str, notify=nameChanged)
    def name(self) -> str:
        return self._name

    def setName(self, name: str) -> None:
        self.updateName(name)

    def updateName(self, name: str) -> None:
        if self._name != name:
            self._name = name
            self.nameChanged.emit()

    ##  Update the bed temperature. This only changes it locally.
    def updateBedTemperature(self, temperature: float) -> None:
        if self._bed_temperature != temperature:
            self._bed_temperature = temperature
            self.bedTemperatureChanged.emit()

    def updateTargetBedTemperature(self, temperature: float) -> None:
        if self._target_bed_temperature != temperature:
            self._target_bed_temperature = temperature
            self.targetBedTemperatureChanged.emit()

    ##  Set the target bed temperature. This ensures that it's actually sent to the remote.
    @pyqtSlot(float)
    def setTargetBedTemperature(self, temperature: float) -> None:
        self._controller.setTargetBedTemperature(self, temperature)
        self.updateTargetBedTemperature(temperature)

    def updateActivePrintJob(
            self, print_job: Optional["PrintJobOutputModel"]) -> None:
        if self._active_print_job != print_job:
            old_print_job = self._active_print_job

            if print_job is not None:
                print_job.updateAssignedPrinter(self)
            self._active_print_job = print_job

            if old_print_job is not None:
                old_print_job.updateAssignedPrinter(None)
            self.activePrintJobChanged.emit()

    def updateState(self, printer_state: str) -> None:
        if self._printer_state != printer_state:
            self._printer_state = printer_state
            self.stateChanged.emit()

    @pyqtProperty(QObject, notify=activePrintJobChanged)
    def activePrintJob(self) -> Optional["PrintJobOutputModel"]:
        return self._active_print_job

    @pyqtProperty(str, notify=stateChanged)
    def state(self) -> str:
        return self._printer_state

    @pyqtProperty(float, notify=bedTemperatureChanged)
    def bedTemperature(self) -> float:
        return self._bed_temperature

    @pyqtProperty(float, notify=targetBedTemperatureChanged)
    def targetBedTemperature(self) -> float:
        return self._target_bed_temperature

    # Does the printer support pre-heating the bed at all
    @pyqtProperty(bool, constant=True)
    def canPreHeatBed(self) -> bool:
        if self._controller:
            return self._controller.can_pre_heat_bed
        return False

    # Does the printer support pre-heating the bed at all
    @pyqtProperty(bool, constant=True)
    def canPreHeatHotends(self) -> bool:
        if self._controller:
            return self._controller.can_pre_heat_hotends
        return False

    # Does the printer support sending raw G-code at all
    @pyqtProperty(bool, constant=True)
    def canSendRawGcode(self) -> bool:
        if self._controller:
            return self._controller.can_send_raw_gcode
        return False

    # Does the printer support pause at all
    @pyqtProperty(bool, constant=True)
    def canPause(self) -> bool:
        if self._controller:
            return self._controller.can_pause
        return False

    # Does the printer support abort at all
    @pyqtProperty(bool, constant=True)
    def canAbort(self) -> bool:
        if self._controller:
            return self._controller.can_abort
        return False

    # Does the printer support manual control at all
    @pyqtProperty(bool, constant=True)
    def canControlManually(self) -> bool:
        if self._controller:
            return self._controller.can_control_manually
        return False

    # Does the printer support upgrading firmware
    @pyqtProperty(bool, notify=canUpdateFirmwareChanged)
    def canUpdateFirmware(self) -> bool:
        if self._controller:
            return self._controller.can_update_firmware
        return False

    # Stub to connect UM.Signal to pyqtSignal
    def _onControllerCanUpdateFirmwareChanged(self) -> None:
        self.canUpdateFirmwareChanged.emit()

    # Returns the configuration (material, variant and buildplate) of the current printer
    @pyqtProperty(QObject, notify=configurationChanged)
    def printerConfiguration(self) -> Optional[PrinterConfigurationModel]:
        if self._printer_configuration.isValid():
            return self._printer_configuration
        return None
Пример #23
0
class MachineErrorChecker(QObject):
    def __init__(self, parent=None):
        super().__init__(parent)

        self._global_stack = None

        self._has_errors = True  # Result of the error check, indicating whether there are errors in the stack
        self._error_keys = set()  # A set of settings keys that have errors
        self._error_keys_in_progress = set(
        )  # The variable that stores the results of the currently in progress check

        self._stacks_and_keys_to_check = None  # a FIFO queue of tuples (stack, key) to check for errors

        self._need_to_check = False  # Whether we need to schedule a new check or not. This flag is set when a new
        # error check needs to take place while there is already one running at the moment.
        self._check_in_progress = False  # Whether there is an error check running in progress at the moment.

        self._application = Application.getInstance()
        self._machine_manager = self._application.getMachineManager()

        self._start_time = 0  # measure checking time

        # This timer delays the starting of error check so we can react less frequently if the user is frequently
        # changing settings.
        self._error_check_timer = QTimer(self)
        self._error_check_timer.setInterval(100)
        self._error_check_timer.setSingleShot(True)

    def initialize(self) -> None:
        self._error_check_timer.timeout.connect(self._rescheduleCheck)

        # Reconnect all signals when the active machine gets changed.
        self._machine_manager.globalContainerChanged.connect(
            self._onMachineChanged)

        # Whenever the machine settings get changed, we schedule an error check.
        self._machine_manager.globalContainerChanged.connect(
            self.startErrorCheck)
        self._machine_manager.globalValueChanged.connect(self.startErrorCheck)

        self._onMachineChanged()

    def _onMachineChanged(self) -> None:
        if self._global_stack:
            self._global_stack.propertyChanged.disconnect(
                self.startErrorCheckPropertyChanged)
            self._global_stack.containersChanged.disconnect(
                self.startErrorCheck)

            for extruder in self._global_stack.extruders.values():
                extruder.propertyChanged.disconnect(
                    self.startErrorCheckPropertyChanged)
                extruder.containersChanged.disconnect(self.startErrorCheck)

        self._global_stack = self._machine_manager.activeMachine

        if self._global_stack:
            self._global_stack.propertyChanged.connect(
                self.startErrorCheckPropertyChanged)
            self._global_stack.containersChanged.connect(self.startErrorCheck)

            for extruder in self._global_stack.extruders.values():
                extruder.propertyChanged.connect(
                    self.startErrorCheckPropertyChanged)
                extruder.containersChanged.connect(self.startErrorCheck)

    hasErrorUpdated = pyqtSignal()
    needToWaitForResultChanged = pyqtSignal()
    errorCheckFinished = pyqtSignal()

    @pyqtProperty(bool, notify=hasErrorUpdated)
    def hasError(self) -> bool:
        return self._has_errors

    @pyqtProperty(bool, notify=needToWaitForResultChanged)
    def needToWaitForResult(self) -> bool:
        return self._need_to_check or self._check_in_progress

    #   Start the error check for property changed
    #   this is seperate from the startErrorCheck because it ignores a number property types
    def startErrorCheckPropertyChanged(self, key, property_name):
        if property_name != "value":
            return
        self.startErrorCheck()

    # Starts the error check timer to schedule a new error check.
    def startErrorCheck(self, *args) -> None:
        if not self._check_in_progress:
            self._need_to_check = True
            self.needToWaitForResultChanged.emit()
        self._error_check_timer.start()

    # This function is called by the timer to reschedule a new error check.
    # If there is no check in progress, it will start a new one. If there is any, it sets the "_need_to_check" flag
    # to notify the current check to stop and start a new one.
    def _rescheduleCheck(self) -> None:
        if self._check_in_progress and not self._need_to_check:
            self._need_to_check = True
            self.needToWaitForResultChanged.emit()
            return

        self._error_keys_in_progress = set()
        self._need_to_check = False
        self.needToWaitForResultChanged.emit()

        global_stack = self._machine_manager.activeMachine
        if global_stack is None:
            Logger.log("i", "No active machine, nothing to check.")
            return

        # Populate the (stack, key) tuples to check
        self._stacks_and_keys_to_check = deque()
        for stack in global_stack.extruders.values():
            for key in stack.getAllKeys():
                self._stacks_and_keys_to_check.append((stack, key))

        self._application.callLater(self._checkStack)
        self._start_time = time.time()
        Logger.log("d", "New error check scheduled.")

    def _checkStack(self) -> None:
        if self._need_to_check:
            Logger.log(
                "d",
                "Need to check for errors again. Discard the current progress and reschedule a check."
            )
            self._check_in_progress = False
            self._application.callLater(self.startErrorCheck)
            return

        self._check_in_progress = True

        # If there is nothing to check any more, it means there is no error.
        if not self._stacks_and_keys_to_check:
            # Finish
            self._setResult(False)
            return

        # Get the next stack and key to check
        stack, key = self._stacks_and_keys_to_check.popleft()

        enabled = stack.getProperty(key, "enabled")
        if not enabled:
            self._application.callLater(self._checkStack)
            return

        validation_state = stack.getProperty(key, "validationState")
        if validation_state is None:
            # Setting is not validated. This can happen if there is only a setting definition.
            # We do need to validate it, because a setting definitions value can be set by a function, which could
            # be an invalid setting.
            definition = stack.getSettingDefinition(key)
            validator_type = SettingDefinition.getValidatorForType(
                definition.type)
            if validator_type:
                validator = validator_type(key)
                validation_state = validator(stack)
        if validation_state in (ValidatorState.Exception,
                                ValidatorState.MaximumError,
                                ValidatorState.MinimumError,
                                ValidatorState.Invalid):
            # Finish
            self._setResult(True)
            return

        # Schedule the check for the next key
        self._application.callLater(self._checkStack)

    def _setResult(self, result: bool) -> None:
        if result != self._has_errors:
            self._has_errors = result
            self.hasErrorUpdated.emit()
            self._machine_manager.stacksValidationChanged.emit()
        self._need_to_check = False
        self._check_in_progress = False
        self.needToWaitForResultChanged.emit()
        self.errorCheckFinished.emit()
        Logger.log("i", "Error check finished, result = %s, time = %0.1fs",
                   result,
                   time.time() - self._start_time)
Пример #24
0
class TabWidget(QTabWidget):
    """The tab widget used for TabbedBrowser.

    Signals:
        tab_index_changed: Emitted when the current tab was changed.
                           arg 0: The index of the tab which is now focused.
                           arg 1: The total count of tabs.
    """

    tab_index_changed = pyqtSignal(int, int)

    def __init__(self, win_id, parent=None):
        super().__init__(parent)
        bar = TabBar(win_id, self)
        self.setStyle(TabBarStyle())
        self.setTabBar(bar)
        bar.tabCloseRequested.connect(self.tabCloseRequested)
        bar.tabMoved.connect(
            functools.partial(QTimer.singleShot, 0, self.update_tab_titles))
        bar.currentChanged.connect(self._on_current_changed)
        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        self.setDocumentMode(True)
        self.setElideMode(Qt.ElideRight)
        self.setUsesScrollButtons(True)
        bar.setDrawBase(False)
        self.init_config()
        objreg.get('config').changed.connect(self.init_config)

    @config.change_filter('tabs')
    def init_config(self):
        """Initialize attributes based on the config."""
        if self is None:  # pragma: no cover
            # WORKAROUND for PyQt 5.2
            return
        tabbar = self.tabBar()
        self.setMovable(config.get('tabs', 'movable'))
        self.setTabsClosable(False)
        position = config.get('tabs', 'position')
        selection_behavior = config.get('tabs', 'select-on-remove')
        self.setTabPosition(position)
        tabbar.vertical = position in [QTabWidget.West, QTabWidget.East]
        tabbar.setSelectionBehaviorOnRemove(selection_behavior)
        tabbar.refresh()

    def set_tab_indicator_color(self, idx, color):
        """Set the tab indicator color.

        Args:
            idx: The tab index.
            color: A QColor.
        """
        bar = self.tabBar()
        bar.set_tab_data(idx, 'indicator-color', color)
        bar.update(bar.tabRect(idx))

    def set_tab_pinned(self,
                       tab: QWidget,
                       pinned: bool,
                       *,
                       loading: bool = False) -> None:
        """Set the tab status as pinned.

        Args:
            tab: The tab to pin
            pinned: Pinned tab state to set.
            loading: Whether to ignore current data state when
                     counting pinned_count.
        """
        bar = self.tabBar()
        idx = self.indexOf(tab)

        # Only modify pinned_count if we had a change
        # always modify pinned_count if we are loading
        if tab.data.pinned != pinned or loading:
            if pinned:
                bar.pinned_count += 1
            elif not pinned:
                bar.pinned_count -= 1

        bar.set_tab_data(idx, 'pinned', pinned)
        tab.data.pinned = pinned
        self.update_tab_title(idx)

        bar.refresh()

    def tab_indicator_color(self, idx):
        """Get the tab indicator color for the given index."""
        return self.tabBar().tab_indicator_color(idx)

    def set_page_title(self, idx, title):
        """Set the tab title user data."""
        self.tabBar().set_tab_data(idx, 'page-title', title)
        self.update_tab_title(idx)

    def page_title(self, idx):
        """Get the tab title user data."""
        return self.tabBar().page_title(idx)

    def update_tab_title(self, idx):
        """Update the tab text for the given tab."""
        tab = self.widget(idx)
        fields = self.get_tab_fields(idx)
        fields['title'] = fields['title'].replace('&', '&&')
        fields['index'] = idx + 1

        fmt = config.get('tabs', 'title-format')
        fmt_pinned = config.get('tabs', 'title-format-pinned')

        if tab.data.pinned:
            title = '' if fmt_pinned is None else fmt_pinned.format(**fields)
        else:
            title = '' if fmt is None else fmt.format(**fields)

        self.tabBar().setTabText(idx, title)

    def get_tab_fields(self, idx):
        """Get the tab field data."""
        tab = self.widget(idx)
        if tab is None:
            log.misc.debug("Got None-tab in get_tab_fields!")

        page_title = self.page_title(idx)

        fields = {}
        fields['id'] = tab.tab_id
        fields['title'] = page_title
        fields['title_sep'] = ' - ' if page_title else ''
        fields['perc_raw'] = tab.progress()
        fields['backend'] = objects.backend.name
        fields['private'] = ' [Private Mode] ' if tab.private else ''

        if tab.load_status() == usertypes.LoadStatus.loading:
            fields['perc'] = '[{}%] '.format(tab.progress())
        else:
            fields['perc'] = ''

        try:
            fields['host'] = self.tab_url(idx).host()
        except qtutils.QtValueError:
            fields['host'] = ''

        y = tab.scroller.pos_perc()[1]
        if y is None:
            scroll_pos = '???'
        elif y <= 0:
            scroll_pos = 'top'
        elif y >= 100:
            scroll_pos = 'bot'
        else:
            scroll_pos = '{:2}%'.format(y)

        fields['scroll_pos'] = scroll_pos
        return fields

    def update_tab_titles(self, section='tabs', option='title-format'):
        """Update all texts."""
        if section == 'tabs' and option in [
                'title-format', 'title-format-pinned'
        ]:
            for idx in range(self.count()):
                self.update_tab_title(idx)

    def tabInserted(self, idx):
        """Update titles when a tab was inserted."""
        super().tabInserted(idx)
        self.update_tab_titles()

    def tabRemoved(self, idx):
        """Update titles when a tab was removed."""
        super().tabRemoved(idx)
        self.update_tab_titles()

    def addTab(self, page, icon_or_text, text_or_empty=None):
        """Override addTab to use our own text setting logic.

        Unfortunately QTabWidget::addTab has these two overloads:
            - QWidget * page, const QIcon & icon, const QString & label
            - QWidget * page, const QString & label

        This means we'll get different arguments based on the chosen overload.

        Args:
            page: The QWidget to add.
            icon_or_text: Either the QIcon to add or the label.
            text_or_empty: Either the label or None.

        Return:
            The index of the newly added tab.
        """
        if text_or_empty is None:
            icon = None
            text = icon_or_text
            new_idx = super().addTab(page, '')
        else:
            icon = icon_or_text
            text = text_or_empty
            new_idx = super().addTab(page, icon, '')
        self.set_page_title(new_idx, text)
        return new_idx

    def insertTab(self, idx, page, icon_or_text, text_or_empty=None):
        """Override insertTab to use our own text setting logic.

        Unfortunately QTabWidget::insertTab has these two overloads:
            - int index, QWidget * page, const QIcon & icon,
              const QString & label
            - int index, QWidget * page, const QString & label

        This means we'll get different arguments based on the chosen overload.

        Args:
            idx: Where to insert the widget.
            page: The QWidget to add.
            icon_or_text: Either the QIcon to add or the label.
            text_or_empty: Either the label or None.

        Return:
            The index of the newly added tab.
        """
        if text_or_empty is None:
            icon = None
            text = icon_or_text
            new_idx = super().insertTab(idx, page, '')
        else:
            icon = icon_or_text
            text = text_or_empty
            new_idx = super().insertTab(idx, page, icon, '')
        self.set_page_title(new_idx, text)
        return new_idx

    @pyqtSlot(int)
    def _on_current_changed(self, index):
        """Emit the tab_index_changed signal if the current tab changed."""
        self.tabBar().on_current_changed()
        self.tab_index_changed.emit(index, self.count())

    def tab_url(self, idx):
        """Get the URL of the tab at the given index.

        Return:
            The tab URL as QUrl.
        """
        tab = self.widget(idx)
        if tab is None:
            url = QUrl()
        else:
            url = tab.url()
        # It's possible for url to be invalid, but the caller will handle that.
        qtutils.ensure_valid(url)
        return url
Пример #25
0
class WebHistory(sql.SqlTable):
    """The global history of visited pages.

    Attributes:
        completion: A CompletionHistory instance.
        metainfo: A CompletionMetaInfo instance.
        _progress: A HistoryProgress instance.
    """

    # All web history cleared
    history_cleared = pyqtSignal()
    # one url cleared
    url_cleared = pyqtSignal(QUrl)

    def __init__(self,
                 database: sql.Database,
                 progress: HistoryProgress,
                 parent: Optional[QObject] = None) -> None:
        super().__init__(database,
                         "History", ['url', 'title', 'atime', 'redirect'],
                         constraints={
                             'url': 'NOT NULL',
                             'title': 'NOT NULL',
                             'atime': 'NOT NULL',
                             'redirect': 'NOT NULL'
                         },
                         parent=parent)
        self._progress = progress
        # Store the last saved url to avoid duplicate immediate saves.
        self._last_url = None

        self.completion = CompletionHistory(database, parent=self)
        self.metainfo = CompletionMetaInfo(database, parent=self)

        try:
            rebuild_completion = self.metainfo['force_rebuild']
        except sql.BugError:  # pragma: no cover
            log.sql.warning("Failed to access meta info, trying to recover...",
                            exc_info=True)
            self.metainfo.try_recover()
            rebuild_completion = self.metainfo['force_rebuild']

        if self.database.user_version_changed():
            with self.database.transaction():
                # If the DB user version changed, run a full cleanup and rebuild the
                # completion history.
                #
                # In the future, this could be improved to only be done when actually
                # needed - but version changes happen very infrequently, rebuilding
                # everything gives us less corner-cases to deal with, and we can run a
                # VACUUM to make things smaller.
                self._cleanup_history()
                rebuild_completion = True
                self.database.upgrade_user_version()

        # Get a string of all patterns
        patterns = config.instance.get_str('completion.web_history.exclude')

        # If patterns changed, update them in database and rebuild completion
        if self.metainfo['excluded_patterns'] != patterns:
            self.metainfo['excluded_patterns'] = patterns
            rebuild_completion = True

        if rebuild_completion and self:
            # If no history exists, we don't need to spawn a dialog for
            # cleaning it up.
            self._rebuild_completion()

        self.create_index('HistoryIndex', 'url')
        self.create_index('HistoryAtimeIndex', 'atime')
        self._contains_query = self.contains_query('url')
        self._between_query = self.database.query(
            'SELECT * FROM History '
            'where not redirect '
            'and not url like "qute://%" '
            'and atime > :earliest '
            'and atime <= :latest '
            'ORDER BY atime desc')

        self._before_query = self.database.query('SELECT * FROM History '
                                                 'where not redirect '
                                                 'and not url like "qute://%" '
                                                 'and atime <= :latest '
                                                 'ORDER BY atime desc '
                                                 'limit :limit offset :offset')

    def __repr__(self):
        return utils.get_repr(self, length=len(self))

    def __contains__(self, url):
        return self._contains_query.run(val=url).value()

    @contextlib.contextmanager
    def _handle_sql_errors(self):
        try:
            yield
        except sql.KnownError as e:
            message.error(f"Failed to write history: {e.text()}")

    def _is_excluded_from_completion(self, url):
        """Check if the given URL is excluded from the completion."""
        patterns = config.cache['completion.web_history.exclude']
        return any(pattern.matches(url) for pattern in patterns)

    def _is_excluded_entirely(self, url):
        """Check if the given URL is excluded from the entire history.

        This is the case for URLs which can't be visited at a later point; or which are
        usually excessively long.

        NOTE: If you add new filters here, it might be a good idea to adjust the
        _USER_VERSION code and _cleanup_history so that older histories get cleaned up
        accordingly as well.
        """
        return (url.scheme() in {'data', 'view-source'} or
                (url.scheme() == 'qute' and url.host() in {'back', 'pdfjs'}))

    def _cleanup_history(self):
        """Do a one-time cleanup of the entire history.

        This is run only once after the v2.0.0 upgrade, based on the database's
        user_version.
        """
        terms = [
            'data:%',
            'view-source:%',
            'qute://back%',
            'qute://pdfjs%',
        ]
        where_clause = ' OR '.join(f"url LIKE '{term}'" for term in terms)
        q = self.database.query(f'DELETE FROM History WHERE {where_clause}')
        entries = q.run()
        log.sql.debug(f"Cleanup removed {entries.rows_affected()} items")

    def _rebuild_completion(self):
        # If this process was interrupted, make sure we trigger a rebuild again
        # at the next start.
        self.metainfo['force_rebuild'] = True

        data: Mapping[str, MutableSequence[str]] = {
            'url': [],
            'title': [],
            'last_atime': []
        }

        self._progress.start(
            "<b>Rebuilding completion...</b><br>"
            "This is a one-time operation and happens because the database version "
            "or <i>completion.web_history.exclude</i> was changed.")

        # Delete old entries
        self.completion.delete_all()
        QApplication.processEvents()

        # Select the latest entry for each url
        q = self.database.query(
            'SELECT url, title, max(atime) AS atime FROM History '
            'WHERE NOT redirect '
            'GROUP BY url ORDER BY atime asc')
        result = q.run()
        QApplication.processEvents()
        entries = list(result)

        self._progress.set_maximum(len(entries))

        for entry in entries:
            self._progress.tick()

            url = QUrl(entry.url)
            if self._is_excluded_from_completion(url):
                continue
            data['url'].append(self._format_completion_url(url))
            data['title'].append(entry.title)
            data['last_atime'].append(entry.atime)

        self._progress.set_maximum(0)

        # We might have caused fragmentation - let's clean up.
        self.database.query('VACUUM').run()
        QApplication.processEvents()

        self.completion.insert_batch(data, replace=True)
        QApplication.processEvents()

        self._progress.finish()
        self.metainfo['force_rebuild'] = False

    def get_recent(self):
        """Get the most recent history entries."""
        return self.select(sort_by='atime', sort_order='desc', limit=100)

    def entries_between(self, earliest, latest):
        """Iterate non-redirect, non-qute entries between two timestamps.

        Args:
            earliest: Omit timestamps earlier than this.
            latest: Omit timestamps later than this.
        """
        self._between_query.run(earliest=earliest, latest=latest)
        return iter(self._between_query)

    def entries_before(self, latest, limit, offset):
        """Iterate non-redirect, non-qute entries occurring before a timestamp.

        Args:
            latest: Omit timestamps more recent than this.
            limit: Max number of entries to include.
            offset: Number of entries to skip.
        """
        self._before_query.run(latest=latest, limit=limit, offset=offset)
        return iter(self._before_query)

    def clear(self):
        """Clear all browsing history."""
        with self._handle_sql_errors():
            self.delete_all()
            self.completion.delete_all()
        self.history_cleared.emit()
        self._last_url = None

    def delete_url(self, url):
        """Remove all history entries with the given url.

        Args:
            url: URL string to delete.
        """
        qurl = QUrl(url)
        qtutils.ensure_valid(qurl)
        self.delete('url', self._format_url(qurl))
        self.completion.delete('url', self._format_completion_url(qurl))
        if self._last_url == url:
            self._last_url = None
        self.url_cleared.emit(qurl)

    @pyqtSlot(QUrl, QUrl, str)
    def add_from_tab(self, url, requested_url, title):
        """Add a new history entry as slot, called from a BrowserTab."""
        if self._is_excluded_entirely(url) or self._is_excluded_entirely(
                requested_url):
            return
        if url.isEmpty():
            # things set via setHtml
            return

        no_formatting = QUrl.UrlFormattingOption(0)
        if (requested_url.isValid()
                and not requested_url.matches(url, no_formatting)):
            # If the url of the page is different than the url of the link
            # originally clicked, save them both.
            self.add_url(requested_url, title, redirect=True)
        if url != self._last_url:
            self.add_url(url, title)
            self._last_url = url

    def add_url(self, url, title="", *, redirect=False, atime=None):
        """Called via add_from_tab when a URL should be added to the history.

        Args:
            url: A url (as QUrl) to add to the history.
            title: The tab title to add.
            redirect: Whether the entry was redirected to another URL
                      (hidden in completion)
            atime: Override the atime used to add the entry
        """
        if not url.isValid():
            log.misc.warning("Ignoring invalid URL being added to history")
            return

        if 'no-sql-history' in objects.debug_flags:
            return

        atime = int(atime) if (atime is not None) else int(time.time())

        with self._handle_sql_errors():
            self.insert({
                'url': self._format_url(url),
                'title': title,
                'atime': atime,
                'redirect': redirect
            })

            if redirect or self._is_excluded_from_completion(url):
                return

            self.completion.insert(
                {
                    'url': self._format_completion_url(url),
                    'title': title,
                    'last_atime': atime
                },
                replace=True)

    def _format_url(self, url):
        return url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)

    def _format_completion_url(self, url):
        return url.toString(QUrl.RemovePassword)
Пример #26
0
class HttpRequestManager(TaskManager):

    __instance = None  # type: Optional[HttpRequestManager]

    internetReachableChanged = pyqtSignal(bool)

    @classmethod
    def getInstance(cls, *args, **kwargs) -> "HttpRequestManager":
        if cls.__instance is None:
            cls.__instance = cls(*args, **kwargs)
        return cls.__instance

    def __init__(self,
                 max_concurrent_requests: int = 4,
                 parent: Optional["QObject"] = None,
                 enable_request_benchmarking: bool = False) -> None:
        if HttpRequestManager.__instance is not None:
            raise RuntimeError("Try to create singleton '%s' more than once" %
                               self.__class__.__name__)
        HttpRequestManager.__instance = self

        super().__init__(parent)

        self._network_manager = QNetworkAccessManager(self)
        self._account_manager = None
        self._is_internet_reachable = True

        # All the requests that have been issued to the QNetworkManager are considered as running concurrently. This
        # number defines the max number of requests that will be issued to the QNetworkManager.
        self._max_concurrent_requests = max_concurrent_requests

        # A FIFO queue for the pending requests.
        self._request_queue = deque()  # type: deque

        # A set of all currently in progress requests
        self._requests_in_progress = set()  # type: Set[HttpRequestData]
        self._request_lock = RLock()
        self._process_requests_scheduled = False

        # Debug options
        #
        # Enabling benchmarking will make the manager to time how much time it takes for a request from start to finish
        # and log them.
        self._enable_request_benchmarking = enable_request_benchmarking

    @pyqtProperty(bool, notify=internetReachableChanged)
    def isInternetReachable(self) -> bool:
        return self._is_internet_reachable

    # Public API for creating an HTTP GET request.
    # Returns an HttpRequestData instance that represents this request.
    def get(self,
            url: str,
            headers_dict: Optional[Dict[str, str]] = None,
            callback: Optional[Callable[["QNetworkReply"], None]] = None,
            error_callback: Optional[Callable[
                ["QNetworkReply", "QNetworkReply.NetworkError"], None]] = None,
            download_progress_callback: Optional[Callable[[int, int],
                                                          None]] = None,
            upload_progress_callback: Optional[Callable[[int, int],
                                                        None]] = None,
            timeout: Optional[float] = None,
            scope: Optional[HttpRequestScope] = None) -> "HttpRequestData":
        return self._createRequest(
            "get",
            url,
            headers_dict=headers_dict,
            callback=callback,
            error_callback=error_callback,
            download_progress_callback=download_progress_callback,
            upload_progress_callback=upload_progress_callback,
            timeout=timeout,
            scope=scope)

    # Public API for creating an HTTP PUT request.
    # Returns an HttpRequestData instance that represents this request.
    def put(self,
            url: str,
            headers_dict: Optional[Dict[str, str]] = None,
            data: Optional[Union[bytes, bytearray]] = None,
            callback: Optional[Callable[["QNetworkReply"], None]] = None,
            error_callback: Optional[Callable[
                ["QNetworkReply", "QNetworkReply.NetworkError"], None]] = None,
            download_progress_callback: Optional[Callable[[int, int],
                                                          None]] = None,
            upload_progress_callback: Optional[Callable[[int, int],
                                                        None]] = None,
            timeout: Optional[float] = None,
            scope: Optional[HttpRequestScope] = None) -> "HttpRequestData":
        return self._createRequest(
            "put",
            url,
            headers_dict=headers_dict,
            data=data,
            callback=callback,
            error_callback=error_callback,
            download_progress_callback=download_progress_callback,
            upload_progress_callback=upload_progress_callback,
            timeout=timeout,
            scope=scope)

    # Public API for creating an HTTP POST request. Returns a unique request ID for this request.
    # Returns an HttpRequestData instance that represents this request.
    def post(self,
             url: str,
             headers_dict: Optional[Dict[str, str]] = None,
             data: Optional[Union[bytes, bytearray]] = None,
             callback: Optional[Callable[["QNetworkReply"], None]] = None,
             error_callback: Optional[
                 Callable[["QNetworkReply", "QNetworkReply.NetworkError"],
                          None]] = None,
             download_progress_callback: Optional[Callable[[int, int],
                                                           None]] = None,
             upload_progress_callback: Optional[Callable[[int, int],
                                                         None]] = None,
             timeout: Optional[float] = None,
             scope: Optional[HttpRequestScope] = None) -> "HttpRequestData":
        return self._createRequest(
            "post",
            url,
            headers_dict=headers_dict,
            data=data,
            callback=callback,
            error_callback=error_callback,
            download_progress_callback=download_progress_callback,
            upload_progress_callback=upload_progress_callback,
            timeout=timeout,
            scope=scope)

    # Public API for creating an HTTP DELETE request.
    # Returns an HttpRequestData instance that represents this request.
    def delete(self,
               url: str,
               headers_dict: Optional[Dict[str, str]] = None,
               callback: Optional[Callable[["QNetworkReply"], None]] = None,
               error_callback: Optional[
                   Callable[["QNetworkReply", "QNetworkReply.NetworkError"],
                            None]] = None,
               download_progress_callback: Optional[Callable[[int, int],
                                                             None]] = None,
               upload_progress_callback: Optional[Callable[[int, int],
                                                           None]] = None,
               timeout: Optional[float] = None,
               scope: Optional[HttpRequestScope] = None) -> "HttpRequestData":
        return self._createRequest(
            "deleteResource",
            url,
            headers_dict=headers_dict,
            callback=callback,
            error_callback=error_callback,
            download_progress_callback=download_progress_callback,
            upload_progress_callback=upload_progress_callback,
            timeout=timeout,
            scope=scope)

    # Public API for aborting a given HttpRequestData. If the request is not pending or in progress, nothing
    # will be done.
    def abortRequest(self, request: "HttpRequestData") -> None:
        with self._request_lock:
            # If the request is currently pending, just remove it from the pending queue.
            if request in self._request_queue:
                self._request_queue.remove(request)

            # If the request is currently in progress, abort it.
            if request in self._requests_in_progress:
                if request.reply is not None and request.reply.isRunning():
                    request.reply.abort()
                    Logger.log("d", "%s aborted", request)

    @staticmethod
    def readJSON(reply: QNetworkReply) -> Any:
        """ Read a Json response into a Python object (list, dict, str depending on json type)

        :return: Python object representing the Json or None in case of error
        """
        try:
            return json.loads(HttpRequestManager.readText(reply))
        except json.decoder.JSONDecodeError:
            Logger.log("w", "Received invalid JSON: " + str(reply.url()))
            return None

    @staticmethod
    def readText(reply: QNetworkReply) -> str:
        """Decode raw reply bytes as utf-8"""
        return bytes(reply.readAll()).decode("utf-8")

    @staticmethod
    def replyIndicatesSuccess(
            reply: QNetworkReply,
            error: Optional["QNetworkReply.NetworkError"] = None) -> bool:
        """Returns whether reply status code indicates success and error is None"""
        return error is None and 200 <= reply.attribute(
            QNetworkRequest.HttpStatusCodeAttribute) < 300

    @staticmethod
    def safeHttpStatus(reply: Optional[QNetworkReply]):
        """Returns the status code or -1 if there isn't any"""
        if reply is None:
            return -1

        return reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) or -1

    @staticmethod
    def qt_network_error_name(error: QNetworkReply.NetworkError):
        """String representation of a NetworkError, eg 'ProtocolInvalidOperationError'"""

        for k, v in QNetworkReply.__dict__.items():
            if v == error:
                return k
        return "Unknown Qt Network error"

    # This function creates a HttpRequestData with the given data and puts it into the pending request queue.
    # If no request processing call has been scheduled, it will schedule it too.
    # Returns an HttpRequestData instance that represents this request.
    def _createRequest(
            self,
            http_method: str,
            url: str,
            headers_dict: Optional[Dict[str, str]] = None,
            data: Optional[Union[bytes, bytearray]] = None,
            callback: Optional[Callable[["QNetworkReply"], None]] = None,
            error_callback: Optional[Callable[
                ["QNetworkReply", "QNetworkReply.NetworkError"], None]] = None,
            download_progress_callback: Optional[Callable[[int, int],
                                                          None]] = None,
            upload_progress_callback: Optional[Callable[[int, int],
                                                        None]] = None,
            timeout: Optional[float] = None,
            scope: Optional[HttpRequestScope] = None) -> "HttpRequestData":
        # Sanity checks
        if timeout is not None and timeout <= 0:
            raise ValueError(
                "Timeout must be a positive number if provided, but [%s] was given"
                % timeout)

        request = QNetworkRequest(QUrl(url))

        # Make sure that Qt handles redirects
        if hasattr(QNetworkRequest, "FollowRedirectsAttribute"):
            # Patch for Qt 5.6-5.8
            request.setAttribute(QNetworkRequest.FollowRedirectsAttribute,
                                 True)
        if hasattr(QNetworkRequest, "RedirectPolicyAttribute"):
            # Patch for Qt 5.9+
            request.setAttribute(QNetworkRequest.RedirectPolicyAttribute, True)

        # Set headers
        if headers_dict is not None:
            for key, value in headers_dict.items():
                request.setRawHeader(key.encode("utf-8"),
                                     value.encode("utf-8"))

        if scope is not None:
            scope.requestHook(request)

        # Generate a unique request ID
        request_id = uuid.uuid4().hex

        # Create the request data
        request_data = HttpRequestData(
            request_id,
            http_method=http_method,
            request=request,
            data=data,
            manager_timeout_callback=self._onRequestTimeout,
            callback=callback,
            error_callback=error_callback,
            download_progress_callback=download_progress_callback,
            upload_progress_callback=upload_progress_callback,
            timeout=timeout)

        with self._request_lock:
            self._request_queue.append(request_data)

            # Schedule a call to process pending requests in the queue
            if not self._process_requests_scheduled:
                self.callLater(0, self._processNextRequestsInQueue)
                self._process_requests_scheduled = True

        return request_data

    # For easier debugging, so you know when the call is triggered by the timeout timer.
    def _onRequestTimeout(self, request_data: "HttpRequestData") -> None:
        Logger.log("d", "Request [%s] timeout.", self)

        # Make typing happy
        if request_data.reply is None:
            return

        with self._request_lock:
            if request_data not in self._requests_in_progress:
                return

            request_data.reply.abort()
            request_data.is_aborted_due_to_timeout = True

    # Processes the next requests in the pending queue. This function will issue as many requests to the QNetworkManager
    # as possible but limited by the value "_max_concurrent_requests". It stops if there is no more pending requests.
    def _processNextRequestsInQueue(self) -> None:
        # Process all requests until the max concurrent number is hit or there's no more requests to process.
        while True:
            with self._request_lock:
                # Do nothing if there's no more requests to process
                if not self._request_queue:
                    self._process_requests_scheduled = False
                    return

                # Do not exceed the max request limit
                if len(self._requests_in_progress
                       ) >= self._max_concurrent_requests:
                    self._process_requests_scheduled = False
                    return

                # Fetch the next request and process
                next_request_data = self._request_queue.popleft()
            self._processRequest(cast(HttpRequestData, next_request_data))

    # Processes the given HttpRequestData by issuing the request using QNetworkAccessManager and moves the
    # request into the currently in-progress list.
    def _processRequest(self, request_data: "HttpRequestData") -> None:
        now = time.time()

        # Get the right http_method function and prepare arguments.
        method = getattr(self._network_manager, request_data.http_method)
        args = [request_data.request]
        if request_data.data is not None:
            args.append(request_data.data)

        # Issue the request and add the reply into the currently in-progress requests set
        reply = method(*args)
        request_data.reply = reply

        # Connect callback signals
        reply.error.connect(
            lambda err, rd=request_data: self._onRequestError(rd, err),
            type=Qt.QueuedConnection)
        reply.finished.connect(
            lambda rd=request_data: self._onRequestFinished(rd),
            type=Qt.QueuedConnection)

        # Only connect download/upload progress callbacks when necessary to reduce CPU usage.
        if request_data.download_progress_callback is not None or request_data.timeout is not None:
            reply.downloadProgress.connect(
                request_data.onDownloadProgressCallback,
                type=Qt.QueuedConnection)
        if request_data.upload_progress_callback is not None or request_data.timeout is not None:
            reply.uploadProgress.connect(request_data.onUploadProgressCallback,
                                         type=Qt.QueuedConnection)

        with self._request_lock:
            self._requests_in_progress.add(request_data)
            request_data.setStartTime(now)

    def _onRequestError(self, request_data: "HttpRequestData",
                        error: "QNetworkReply.NetworkError") -> None:
        error_string = None
        if request_data.reply is not None:
            error_string = request_data.reply.errorString()

        if error == QNetworkReply.UnknownNetworkError:
            self._setInternetReachable(False)
            # manager seems not always able to recover from a total loss of network access, so re-create it
            self._network_manager = QNetworkAccessManager(self)

        Logger.log("d", "%s got an error %s, %s", request_data, error,
                   error_string)

        with self._request_lock:
            # Safeguard: make sure that we have the reply in the currently in-progress requests set
            if request_data not in self._requests_in_progress:
                # TODO: ERROR, should not happen
                Logger.log("e", "%s not found in the in-progress set",
                           request_data)
                pass
            else:
                # Disconnect callback signals
                if request_data.reply is not None:
                    if request_data.download_progress_callback is not None:
                        request_data.reply.downloadProgress.disconnect(
                            request_data.onDownloadProgressCallback)
                    if request_data.upload_progress_callback is not None:
                        request_data.reply.uploadProgress.disconnect(
                            request_data.onUploadProgressCallback)

                    request_data.setDone()
                    self._requests_in_progress.remove(request_data)

        # Schedule the error callback if there is one
        if request_data.error_callback is not None:
            self.callLater(0, request_data.error_callback, request_data.reply,
                           error)

        # Continue to process the next request
        self._processNextRequestsInQueue()

    def _onRequestFinished(self, request_data: "HttpRequestData") -> None:
        # See https://doc.qt.io/archives/qt-5.10/qnetworkreply.html#abort
        # Calling QNetworkReply.abort() will also trigger finished(), so we need to know if a request was finished or
        # aborted. This can be done by checking if the error is QNetworkReply.OperationCanceledError. If a request was
        # aborted due to timeout, the request's HttpRequestData.is_aborted_due_to_timeout will be set to True.
        #
        # We do nothing if the request was aborted or and error was detected because an error callback will also
        # be triggered by Qt.
        reply = request_data.reply
        if reply is not None:
            reply_error = reply.error()  # error() must only be called once
            if reply_error != QNetworkReply.NoError:
                if reply_error == QNetworkReply.OperationCanceledError:
                    Logger.log("d", "%s was aborted, do nothing", request_data)

                # stop processing for any kind of error
                return

        # No error? Internet is reachable
        self._setInternetReachable(True)

        if self._enable_request_benchmarking:
            time_spent = None  # type: Optional[float]
            if request_data.start_time is not None:
                time_spent = time.time() - request_data.start_time
            Logger.log(
                "d",
                "Request [%s] finished, took %s seconds, pending for %s seconds",
                request_data, time_spent, request_data.pending_time)

        with self._request_lock:
            # Safeguard: make sure that we have the reply in the currently in-progress requests set.
            if request_data not in self._requests_in_progress:
                # This can happen if a request has been aborted. The finished() signal will still be triggered at the
                # end. In this case, do nothing with this request.
                Logger.log("e", "%s not found in the in-progress set",
                           request_data)
            else:
                # Disconnect callback signals
                if reply is not None:
                    # Even after the request was successfully finished, an error may still be emitted if
                    # the network connection is lost seconds later. Bug in Qt? Fixes CURA-7349
                    reply.error.disconnect()

                    if request_data.download_progress_callback is not None:
                        reply.downloadProgress.disconnect(
                            request_data.onDownloadProgressCallback)
                    if request_data.upload_progress_callback is not None:
                        reply.uploadProgress.disconnect(
                            request_data.onUploadProgressCallback)

                request_data.setDone()
                self._requests_in_progress.remove(request_data)

        # Schedule the callback if there is one
        if request_data.callback is not None:
            self.callLater(0, request_data.callback, reply)

        # Continue to process the next request
        self._processNextRequestsInQueue()

    def _setInternetReachable(self, reachable: bool):
        if reachable != self._is_internet_reachable:
            self._is_internet_reachable = reachable
            self.internetReachableChanged.emit(reachable)
Пример #27
0
class MruList(QObject):
    """Manages a most-recently-used list of strings"""

    # The list contents have changed - arguments are str:old_value, str:new_value
    changed = pyqtSignal(list, list)

    def __init__(self, items=None, max_len=8):
        """Constructor
        :type items: list of str
        :type max_len: int
        """
        super(MruList, self).__init__()

        if items is None:
            items = []
        assert max_len >= len(items)
        self._items = items
        self._max_len = max_len

    def __repr__(self):
        """Returns representation of the object
        :rtype str - e.g.: MruList(['/home/bob/clip2.wav', '/home/bob/clip1.wav'], 8)
        """
        return "{}({}, {})".format(self.__class__.__name__, self._items,
                                   self._max_len)

    def clear(self):
        """Erases all strings in the list"""
        old_value = self._items
        self._items = []

        if old_value != self.items:
            self.changed.emit(old_value, self._items)

    def contains(self, text):
        """Returns True if the specified string is in the list
        :type text: str - the string to locate
        :rtype bool - True if the specified string is in the list, otherwise False
        """
        lc_text = text.lower()
        lc_items = [item.lower() for item in self._items]

        return lc_text in lc_items

    def count(self):
        """Returns a count of strings in the list
        :rtype int - The size of the MRU list (0..max_len)
        """
        return len(self._items)

    def get(self, index):
        """Returns a string by index
        :type index: int - index of the string (0..[max_len-1])
        :rtype str - The string at the specified index, or an empty string if none exists
        """
        if 0 <= index < len(self._items):
            # ...remove and return it
            return self._items[index]

        return ''

    def index_of(self, text):
        """Returns the index of the specified string
        :type text: str - the string to locate
        :rtype int - The index of the specified string (0..[max_len-1]), or -1 if not found
        """
        lc_text = text.lower()
        lc_items = [item.lower() for item in self._items]

        return lc_items.index(lc_text) if lc_text in lc_items else -1

    def push(self, text):
        """Adds the specified string to the front of the list, or moves it to the front if it's already in the list.
        :type text: str - the string to add
        """
        # None, empty, or whitespace-only text is ignored
        text = '' if text is None else text.strip()
        if not text:
            return

        # Get the index of the item in the list, or -1 if not in list
        index = self.index_of(text)

        old_value = list(self._items)

        if index == -1:
            # Insert the new item at the front of the list
            self._items.insert(0, text)

            # truncate list to (max_len) items
            del self._items[self._max_len:]
        # If the item is in the list, but not already at the front...
        elif index > 0:
            # ...move it to the front
            self._items.insert(0, self._items.pop(index))

        if old_value != self._items:
            self.changed.emit(old_value, self._items)

    def pop(self, text):
        """Removes the specified string from the list."""
        # None, empty, or whitespace-only text is ignored
        text = '' if text is None else text.strip()
        if text:
            # Get the index of the item in the list, or -1 if not in list
            index = self.index_of(text)

            # If the item is in the list...
            if index >= 0:
                # ...remove item from the list and return it
                old_value = list(self._items)

                result = self._items.pop(index)

                self.changed.emit(old_value, self._items)

                return result

        return ''
Пример #28
0
class VisualizationTabWidget(Ui_VisualizationTabWidget, QObject):

    viewSkeleton = pyqtSignal(bool)
    viewGraph = pyqtSignal(bool)
    viewBoth = pyqtSignal(bool)

    backgroundColorChanged = pyqtSignal(float, float, float)
    loadMeshSig = pyqtSignal()

    def getHeatmap(self, idx: int):  #idx is option of heatmap
        if idx == 0:
            return [[1.0, 0.0, 0.0, 1.0]]

        else:
            return getColorList(1000, cm.get_cmap(self.heatmapOptions[idx]))

    @pyqtSlot(int)
    def edgeColorizationChanged(self, optionId: int):
        if optionId == 0:  #thickness
            self.graph.colorizeEdgesByThickness()
            pass
        elif optionId == 1:  #width
            self.graph.colorizeEdgesByWidth()
            pass
        elif optionId == 2:  #thickness / width
            self.graph.colorizeEdgesByRatio()
            pass
        elif optionId == 3:  #component
            self.graph.colorizeEdgesByComponent()
            pass

    @pyqtSlot(int)
    def nodeColorizationChanged(self, optionId: int):
        if optionId == 0:  #thickness
            self.graph.colorizeNodesByThickness()
            pass
        elif optionId == 1:  #width
            self.graph.colorizeNodesByWidth()
            pass
        elif optionId == 2:  #degree
            self.graph.colorizeNodesByDegree()
            pass
        elif optionId == 3:  #component
            self.graph.colorizeNodesByComponent()
            pass
        elif optionId == 4:  #flat node color
            self.graph.colorizeNodesByConstantColor()

    @pyqtSlot(int)
    def nodeHeatmapChanged(self, optionId: int):
        heatmap = self.getHeatmap(optionId)
        self.graph.assignNodeHeatmap(heatmap)

    @pyqtSlot(int)
    def edgeHeatmapChanged(self, optionId: int):
        heatmap = self.getHeatmap(optionId)
        self.graph.assignEdgeHeatmap(heatmap)

    @pyqtSlot(int)
    def junctionScaleChanged(self, sliderVal):
        scale = 1.0 * sliderVal / 10.0
        self.graph.setJunctionScale(scale)

    @pyqtSlot(int)
    def endpointScaleChanged(self, sliderVal):
        scale = 1.0 * sliderVal / 10.0
        self.graph.setEndpointScale(scale)

    @pyqtSlot(int)
    def edgeScaleChanged(self, sliderVal):
        scale = 1.0 * sliderVal / 10.0
        scale = max(scale, 1.0)
        self.graph.setEdgeScale(scale)

    @pyqtSlot()
    def edgeColorFloorChanged(self):
        sliderVal = self.edgeColorFloor.value()
        floor = 1.0 * sliderVal / 100.0
        if not self.edgeColorFloor.isSliderDown():
            self.graph.setEdgeColorFloor(floor)

    @pyqtSlot()
    def edgeColorCeilingChanged(self):
        sliderVal = self.edgeColorCeiling.value()
        ceiling = 1.0 * sliderVal / 100.0
        self.graph.setEdgeColorCeiling(ceiling)

    @pyqtSlot()
    def nodeColorFloorChanged(self):
        sliderVal = self.nodeColorFloor.value()
        floor = 1.0 * sliderVal / 100.0
        self.graph.setNodeColorFloor(floor)

    @pyqtSlot()
    def nodeColorCeilingChanged(self):
        sliderVal = self.nodeColorCeiling.value()
        ceiling = 1.0 * sliderVal / 100.0
        self.graph.setNodeColorCeiling(ceiling)

    @pyqtSlot(bool)
    def showJunctionsPressed(self, showJunctions: bool):
        self.graph.showJunctions(showJunctions)

    @pyqtSlot(bool)
    def showEndpointsPressed(self, showEndpoints: bool):
        self.graph.showEndpoints(showEndpoints)

    @pyqtSlot(bool)
    def showEdgesPressed(self, showEdges: bool):
        self.graph.showEdges(showEdges)

    @pyqtSlot(bool)
    def magnifyNonBridgesPressed(self, magnifyNonBridges: bool):
        self.graph.magnifyNonBridges(magnifyNonBridges)

    @pyqtSlot(bool)
    def showOnlyNonBridgesPressed(self, showOnly: bool):
        self.graph.showOnlyNonBridges(showOnly)

    @pyqtSlot(bool)
    def backgroundColorClicked(self, active):
        pickedColor = QtWidgets.QColorDialog.getColor(self.currentBackground,
                                                      self.widget)
        self.backgroundColorChanged.emit(pickedColor.redF(),
                                         pickedColor.greenF(),
                                         pickedColor.blueF())
        self.currentBackground = pickedColor

    @pyqtSlot(bool)
    def constantNodeColorClicked(self, active):
        pickedColor = QtWidgets.QColorDialog.getColor(self.currentNodeColor,
                                                      self.widget)
        self.graph.setConstantNodeColor(pickedColor.redF(),
                                        pickedColor.greenF(),
                                        pickedColor.blueF())
        self.currentNodeColor = pickedColor

    @pyqtSlot(bool)
    def edgeSelectionColorClicked(self, active):
        pickedColor = QtWidgets.QColorDialog.getColor(
            self.currentEdgeSelectionColor, self.widget)
        self.graph.setEdgeSelectionColor(pickedColor.redF(),
                                         pickedColor.greenF(),
                                         pickedColor.blueF())
        self.currentEdgeSelectionColor = pickedColor

    @pyqtSlot(bool)
    def loadMeshClicked(self, active: bool):
        self.loadMeshSig.emit()

    @pyqtSlot(bool)
    def meshColorClicked(self, active: bool):
        pickedColor = QtWidgets.QColorDialog.getColor(self.currentMeshColor,
                                                      self.widget)
        self.graph.setMeshColor(pickedColor.redF(), pickedColor.greenF(),
                                pickedColor.blueF())
        self.currentMeshColor = pickedColor

    @pyqtSlot(bool)
    def displayMeshClicked(self, doShow: bool):
        self.graph.showMesh(doShow)

    @pyqtSlot()
    def meshAlphaChanged(self):
        alphaInt = self.meshAlpha.value()
        alpha = 1.0 * alphaInt / 100.0
        self.graph.setMeshAlpha(alpha)

    def __init__(self, widget, graphObject: mgraph, viewSkeletonButton,
                 viewGraphButton, viewBothButton):
        Ui_VisualizationTabWidget.__init__(self)
        QObject.__init__(self)
        self.setupUi(widget)
        self.widget = widget
        self.graph = graphObject
        self.currentBackground = QColor(255, 255, 255)
        self.currentNodeColor = QColor(0, 0, 0)
        self.currentEdgeSelectionColor = QColor(255, 255, 255)
        self.currentMeshColor = QColor(0, 0, 255)

        self.graph.setConstantNodeColor(self.currentNodeColor.redF(),
                                        self.currentNodeColor.greenF(),
                                        self.currentNodeColor.blueF())
        self.graph.setEdgeSelectionColor(
            self.currentEdgeSelectionColor.redF(),
            self.currentEdgeSelectionColor.greenF(),
            self.currentEdgeSelectionColor.blueF())

        self.graph.setMeshColor(self.currentMeshColor.redF(),
                                self.currentMeshColor.greenF(),
                                self.currentMeshColor.blueF())

        self.edgeColorizationOptions = {}
        self.edgeColorizationOptions[0] = "Thickness"
        self.edgeColorizationOptions[1] = "Width"
        self.edgeColorizationOptions[2] = "Thick/Width"
        self.edgeColorizationOptions[3] = "Component"

        self.nodeColorizationOptions = {}
        self.nodeColorizationOptions[0] = "Thickness"
        self.nodeColorizationOptions[1] = "Width"
        self.nodeColorizationOptions[2] = "Degree"
        self.nodeColorizationOptions[3] = "Component"
        self.nodeColorizationOptions[4] = "Flat Color"
        self.heatmapOptions = {}
        self.heatmapOptions[0] = "None"
        self.heatmapOptions[1] = "viridis"
        self.heatmapOptions[2] = "plasma"
        self.heatmapOptions[3] = "inferno"
        self.heatmapOptions[4] = "magma"
        self.heatmapOptions[5] = "hot"
        self.heatmapOptions[6] = "cool"
        self.heatmapOptions[7] = "gist_heat"
        self.heatmapOptions[8] = "BuGn"
        self.heatmapOptions[9] = "jet"

        for key in self.edgeColorizationOptions:
            self.edgeColorization.addItem(self.edgeColorizationOptions[key])

        for key in self.nodeColorizationOptions:
            self.nodeColorization.addItem(self.nodeColorizationOptions[key])

        for key in self.heatmapOptions:
            self.edgeHeatmapType.addItem(self.heatmapOptions[key])
            self.nodeHeatmapType.addItem(self.heatmapOptions[key])

        self.edgeColorization.currentIndexChanged.connect(
            self.edgeColorizationChanged)
        self.nodeColorization.currentIndexChanged.connect(
            self.nodeColorizationChanged)
        self.edgeHeatmapType.currentIndexChanged.connect(
            self.edgeHeatmapChanged)
        self.nodeHeatmapType.currentIndexChanged.connect(
            self.nodeHeatmapChanged)

        self.edgeColorization.setCurrentIndex(0)
        self.nodeColorization.setCurrentIndex(0)

        self.showEndpoints.toggled.connect(self.showEndpointsPressed)
        self.showJunctions.toggled.connect(self.showJunctionsPressed)
        self.showEdges.toggled.connect(self.showEdgesPressed)
        self.magnifyNonBridges.toggled.connect(self.magnifyNonBridgesPressed)
        self.displayOnlyNonBridges.toggled.connect(
            self.showOnlyNonBridgesPressed)

        self.backgroundColor.clicked.connect(self.backgroundColorClicked)
        self.constantNodeColor.clicked.connect(self.constantNodeColorClicked)
        self.edgeSelectionColor.clicked.connect(self.edgeSelectionColorClicked)

        self.showEndpoints.setChecked(True)
        self.showJunctions.setChecked(True)
        self.showEdges.setChecked(True)
        self.magnifyNonBridges.setChecked(False)

        self.loadMeshButton.clicked.connect(self.loadMeshClicked)
        self.meshColorButton.clicked.connect(self.meshColorClicked)
        self.displayMesh.toggled.connect(self.displayMeshClicked)

        self.displayMesh.setChecked(False)

        # setting slider callbacks and values
        self.edgeScale.valueChanged.connect(self.edgeScaleChanged)
        self.junctionScale.valueChanged.connect(self.junctionScaleChanged)
        self.endpointScale.valueChanged.connect(self.endpointScaleChanged)
        self.edgeColorFloor.sliderReleased.connect(self.edgeColorFloorChanged)
        self.edgeColorCeiling.sliderReleased.connect(
            self.edgeColorCeilingChanged)
        self.nodeColorFloor.sliderReleased.connect(self.nodeColorFloorChanged)
        self.nodeColorCeiling.sliderReleased.connect(
            self.nodeColorCeilingChanged)
        self.meshAlpha.sliderReleased.connect(self.meshAlphaChanged)

        self.edgeColorFloor.setSliderDown(True)
        self.edgeColorCeiling.setSliderDown(True)
        self.nodeColorFloor.setSliderDown(True)
        self.nodeColorCeiling.setSliderDown(True)
        self.meshAlpha.setSliderDown(True)
        self.edgeScale.setValue(20)
        self.junctionScale.setValue(5)
        self.endpointScale.setValue(5)
        self.edgeColorFloor.setValue(0)
        self.edgeColorCeiling.setValue(100)
        self.nodeColorFloor.setValue(0)
        self.nodeColorCeiling.setValue(100)
        self.meshAlpha.setValue(30)
        self.edgeColorFloor.setSliderDown(False)
        self.edgeColorCeiling.setSliderDown(False)
        self.nodeColorFloor.setSliderDown(False)
        self.nodeColorCeiling.setSliderDown(False)
        self.meshAlpha.setSliderDown(False)

        self.viewSkeleton.emit(True)
Пример #29
0
 def mouseReleaseEvent(self, ev):
     self.emit(pyqtSignal('clicked()'))
     return
Пример #30
0
class ShowVideo(QObject):
    VideoSignal = pyqtSignal(QImage)

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

    @pyqtSlot()
    def startVideo(self):

        self._running = True
        config = camera_config_path
        if config is not None:
            with open(config) as json_file:
                config = json.load(json_file)

        output_folder = config['path_dataset']

        path_depth = join(output_folder, "depth")
        path_color = join(output_folder, "color")

        self.make_clean_folder(output_folder)
        self.make_clean_folder(path_depth)
        self.make_clean_folder(path_color)

        # Create a pipeline
        pipeline = rs.pipeline()

        # Create a config and configure the pipeline to stream
        #  different resolutions of color and depth streams
        config = rs.config()

        config.enable_stream(rs.stream.depth, 640, 480, rs.format.z16,
                             self.frame_rate)
        config.enable_stream(rs.stream.color, 640, 480, rs.format.bgr8,
                             self.frame_rate)

        # Start streaming
        profile = pipeline.start(config)
        depth_sensor = profile.get_device().first_depth_sensor()

        # Using preset HighAccuracy for recording

        depth_sensor.set_option(rs.option.visual_preset, Preset.HighAccuracy)

        # Getting the depth sensor's depth scale (see rs-align example for explanation)
        depth_scale = depth_sensor.get_depth_scale()

        # We will not display the background of objects more than
        #  clipping_distance_in_meters meters away
        clipping_distance_in_meters = 3  # 3 meter
        clipping_distance = clipping_distance_in_meters / depth_scale

        # Create an align object
        # rs.align allows us to perform alignment of depth frames to others frames
        # The "align_to" is the stream type to which we plan to align depth frames.
        align_to = rs.stream.color
        align = rs.align(align_to)

        # Streaming loop
        frame_count = 0
        try:
            while self._running:
                QCoreApplication.processEvents()
                # Get frameset of color and depth
                frames = pipeline.wait_for_frames()

                # Align the depth frame to color frame
                aligned_frames = align.process(frames)

                # Get aligned frames
                aligned_depth_frame = aligned_frames.get_depth_frame()
                color_frame = aligned_frames.get_color_frame()

                # Validate that both frames are valid
                if not aligned_depth_frame or not color_frame:
                    continue

                depth_image = np.asanyarray(aligned_depth_frame.get_data())
                color_image = np.asanyarray(color_frame.get_data())

                if frame_count == 0:
                    self.save_intrinsic_as_json(
                        join(output_folder, "camera_intrinsic.json"),
                        color_frame)
                cv2.imwrite("%s/%06d.png" % \
                            (path_depth, frame_count), depth_image)
                cv2.imwrite("%s/%06d.jpg" % \
                            (path_color, frame_count), color_image)
                print("Saved color + depth image %06d" % frame_count)
                frame_count += 1

                # Remove background - Set pixels further than clipping_distance to grey
                grey_color = 153
                # depth image is 1 channel, color is 3 channels
                depth_image_3d = np.dstack(
                    (depth_image, depth_image, depth_image))
                bg_removed = np.where((depth_image_3d > clipping_distance) | \
                                      (depth_image_3d <= 0), grey_color, color_image)

                # Render images
                depth_colormap = cv2.applyColorMap(
                    cv2.convertScaleAbs(depth_image, alpha=0.09),
                    cv2.COLORMAP_JET)
                images = np.hstack((bg_removed, depth_colormap))
                color_swapped_image = cv2.cvtColor(images, cv2.COLOR_BGR2RGB)
                height, width, _ = images.shape

                qt_image = QImage(color_swapped_image, width, height,
                                  color_swapped_image.strides[0],
                                  QImage.Format_RGB888).scaledToWidth(720)

                # cv2.namedWindow('Recorder Realsense', cv2.WINDOW_AUTOSIZE)
                # cv2.imshow('Recorder Realsense', images)
                key = cv2.waitKey(1)

                # if 'esc' button pressed, escape loop and exit program
                if key == 27:
                    cv2.destroyAllWindows()
                    break
                self.VideoSignal.emit(qt_image)
        finally:
            pipeline.stop()

    def make_clean_folder(self, path_folder):
        if not exists(path_folder):
            makedirs(path_folder)
        else:

            shutil.rmtree(path_folder)
            makedirs(path_folder)

    def save_intrinsic_as_json(self, filename, frame):
        intrinsics = frame.profile.as_video_stream_profile().intrinsics
        with open(filename, 'w') as outfile:
            obj = json.dump(
                {
                    'width':
                    intrinsics.width,
                    'height':
                    intrinsics.height,
                    'intrinsic_matrix': [
                        intrinsics.fx, 0, 0, 0, intrinsics.fy, 0,
                        intrinsics.ppx, intrinsics.ppy, 1
                    ]
                },
                outfile,
                indent=4)

    @pyqtSlot()
    def stopVideo(self):
        # print ('stop signal received, switching while loop condition to false')
        self._running = False
Пример #31
0
class TraitsTabWidget(Ui_TraitsTabWidget, QObject):
    modeChangeSig = pyqtSignal(int)
    loadTraitsSig = pyqtSignal()
    saveTraitsSig = pyqtSignal()

    @pyqtSlot(bool)
    def loadTraitsPressed(self, pressed: bool):
        self.changeMode(SelectPrimaryBranchesMode)
        self.loadTraitsSig.emit()
        self.updateWidget()

    @pyqtSlot(bool)
    def saveTraitsPressed(self, pressed: bool):
        self.saveTraitsSig.emit()

    @pyqtSlot(bool)
    def showStemChecked(self, doShow: bool):
        self.showStem = doShow
        if self.graph != None:
            self.graph.setDisplayStem(self.showStem)

    @pyqtSlot(bool)
    def selectStemPressed(self, pressed: bool):
        self.changeMode(SelectStemMode)

    @pyqtSlot(bool)
    def confirmStemPressed(self, pressed: bool):
        if self.mode == SelectStemMode and self.graph:
            self.graph.selectStemOperation()
            self.updateWidget()

    @pyqtSlot(bool)
    def ViewNodeInfoPressed(self, pressed: bool):
        self.changeMode(ViewNodeInfoMode)

    @pyqtSlot(bool)
    def showStemSuggestionChecked(self, doShow: bool):
        if self.graph != None:
            self.graph.setDisplaySuggestedStem(doShow)

    @pyqtSlot(bool)
    def FindStemPressed(self, pressed: bool):
        try:
            self.stemLowThreshold = float(self.LowThresholdLineEdit.text())
        except ValueError:
            self.stemLowThreshold = int(self.LowThresholdLineEdit.text())
        # print(self.stemLowThreshold)
        if self.graph != None:
            self.graph.FindStemOperation(self.stemLowThreshold)
            self.updateWidget()

    @pyqtSlot(bool)
    def FindPrimaryNodePressed(self, Pressed: bool):
        try:
            self.nodeNeighbourRange = float(self.NodeIntervalLineEdit.text())
        except ValueError:
            self.nodeNeighbourRange = int(self.NodeIntervalLineEdit.text())

        try:
            self.Kernel_bandWidth = float(self.BandWidthLineEdit.text())
        except ValueError:
            self.Kernel_bandWidth = int(self.BandWidthLineEdit.text())

        if self.graph != None:
            self.graph.FindPrimaryNodeOperation(self.nodeNeighbourRange,
                                                self.Kernel_bandWidth)
            self.updateWidget()

    @pyqtSlot(bool)
    def showNodeSuggestionChecked(self, doShow: bool):
        if self.graph != None:
            self.graph.setDisplaySuggestedNode(doShow)

    @pyqtSlot(bool)
    def showPrimaryNodesChecked(self, doShow: bool):
        self.showPrimaryNodes = doShow
        if self.graph != None:
            self.graph.setDisplayPrimaryNodes(self.showPrimaryNodes)

    @pyqtSlot(bool)
    def randomColorizePrimaryNodesChecked(self, pressed: bool):
        self.isRandomColorizePrimaryNodes = pressed
        if self.graph != None:
            self.graph.setRandomColorizePrimaryNodes(
                self.isRandomColorizePrimaryNodes)
            self.updateWidget()

    @pyqtSlot(bool)
    def selectStemPrimaryNodePressed(self, pressed: bool):
        self.changeMode(SelectPrimaryNodesMode)

    @pyqtSlot(bool)
    def confirmPrimaryNodesPressed(self, pressed: bool):
        if self.mode == SelectPrimaryNodesMode and self.graph:
            self.graph.selectStemPrimaryNodeOperation()
            self.updateWidget()

    @pyqtSlot(int)
    def currentPrimaryNodeChanged(self, node: int):
        self.currentPrimaryNode = node
        if self.graph != None:
            self.graph.setCurrentPrimaryNode(self.currentPrimaryNode)

    @pyqtSlot(bool)
    def selectPrimaryBranchesPressed(self, pressed: bool):
        self.changeMode(SelectPrimaryBranchesMode)

    @pyqtSlot(bool)
    def confirmPrimaryBranchesPressed(self, pressed: bool):
        if self.mode == SelectPrimaryBranchesMode and self.graph:
            self.graph.selectPrimaryBranchesOperation()
            self.updateWidget()

    @pyqtSlot(bool)
    def removePrimaryBranchesPressed(self, pressed: bool):
        if self.mode == SelectPrimaryBranchesMode and self.graph:
            self.graph.RemovePrimaryBranchesOperation()
            self.updateWidget()

    @pyqtSlot(bool)
    def PrimaryNodeSelectionColorPressed(self, active):
        pickedColor = QtWidgets.QColorDialog.getColor(
            self.currentPrimaryNodeSelectionColor, self.widget)
        self.graph.setCurrentPrimaryNodeSelectionColor(pickedColor.redF(),
                                                       pickedColor.greenF(),
                                                       pickedColor.blueF())
        self.currentPrimaryNodeSelectionColor = pickedColor

    @pyqtSlot(bool)
    def showConfirmedPrimaryBranchesChecked(self, doShow: bool):
        self.showConfirmedPrimaryBranches = doShow
        if self.mode == SelectPrimaryBranchesMode:
            if self.graph != None:
                self.graph.setDisplayConfirmedPrimaryBranches(
                    self.showConfirmedPrimaryBranches)

    @pyqtSlot(bool)
    def showOnlyBranchesOfCurrentPrimaryNodeChecked(self, doShow: bool):
        self.showOnlyBranchesOfCurrentPrimaryNode = doShow
        if self.mode == SelectPrimaryBranchesMode and self.graph != None:
            self.graph.setDisplayOnlyBranchesOfCurrentPrimaryNode(
                self.showOnlyBranchesOfCurrentPrimaryNode)

    @pyqtSlot(bool)
    def showTraitsOnlyChecked(self, doShow: bool):
        self.showTraitsOnly = doShow
        if self.graph != None:
            self.graph.setDisplayTraitsOnly(self.showTraitsOnly)

    @pyqtSlot(bool)
    def SelectSegmentPointPressed(self, pressed: bool):
        self.changeMode(SelectSegmentPointMode)

    @pyqtSlot(bool)
    def ConfirmSegmentPointPressed(self):
        if self.mode == SelectSegmentPointMode and self.graph:
            self.graph.selectSegmentPointOperation()
            self.updateWidget()

    @pyqtSlot(bool)
    def showSelectedSegmentChecked(self, doShow: bool):
        self.showSelectedSegment = doShow
        if self.graph != None:
            self.graph.setDisplaySelectedSegment(self.showSelectedSegment)

    @pyqtSlot()
    def horizontalSliderRadiusChanged(self):
        sliderVal = self.horizontalSliderRadius.value()
        if not self.horizontalSliderRadius.isSliderDown():
            self.graph.setSegmentHorizontalSliderRadius(sliderVal)

    def __init__(self, graphObject: mgraph, widget=None):
        Ui_TraitsTabWidget.__init__(self)
        QObject.__init__(self)
        self.setupUi(widget)
        self.widget = widget
        self.graph = graphObject
        self.mode = NoMode
        self.showStem = False
        self.showPrimaryNodes = False
        self.currentPrimaryNode = 0
        self.showConfirmedPrimaryBranches = False
        self.isRandomColorizePrimaryNodes = False
        self.showOnlyBranchesOfCurrentPrimaryNode = False
        self.showSelectedSegment = False
        self.showTraitsOnly = False

        self.stemLowThreshold = 0
        self.nodeNeighbourRange = 0
        self.Kernel_bandWidth = 0

        self.currentPrimaryNodeSelectionColor = QColor(255, 255, 255)
        self.graph.setCurrentPrimaryNodeSelectionColor(
            self.currentPrimaryNodeSelectionColor.redF(),
            self.currentPrimaryNodeSelectionColor.greenF(),
            self.currentPrimaryNodeSelectionColor.blueF())

        self.loadTraitsButton.clicked.connect(self.loadTraitsPressed)
        self.saveTraitsButton.clicked.connect(self.saveTraitsPressed)

        # manual find stem
        self.showStemCheck.toggled.connect(self.showStemChecked)
        self.SelectStemButton.clicked.connect(self.selectStemPressed)
        self.ConfirmStemButton.clicked.connect(self.confirmStemPressed)
        # automatic find stem
        self.ViewNodeInfoButton.clicked.connect(self.ViewNodeInfoPressed)
        self.showStemSuggestionCheck.toggled.connect(
            self.showStemSuggestionChecked)
        self.FindStemButton.clicked.connect(self.FindStemPressed)

        # manual find primary nodes
        self.showPrimaryNodesCheck.toggled.connect(
            self.showPrimaryNodesChecked)
        self.SelectPriamryNodeButton.clicked.connect(
            self.selectStemPrimaryNodePressed)
        self.ConfirmPrimaryNodesButton.clicked.connect(
            self.confirmPrimaryNodesPressed)
        # automatic find primary nodes
        self.showNodeSuggestionCheck.toggled.connect(
            self.showNodeSuggestionChecked)
        self.FindPrimaryNodesButton.clicked.connect(
            self.FindPrimaryNodePressed)

        self.PrimaryNodeSelectionColorButton.clicked.connect(
            self.PrimaryNodeSelectionColorPressed)
        self.RandomColorizePrimaryNodesCheck.toggled.connect(
            self.randomColorizePrimaryNodesChecked)

        self.CurrentPrimaryNodeCombo.currentIndexChanged.connect(
            self.currentPrimaryNodeChanged)
        self.SelectPrimaryBranchesButton.clicked.connect(
            self.selectPrimaryBranchesPressed)
        self.ConfirmPrimaryBranchesButton.clicked.connect(
            self.confirmPrimaryBranchesPressed)
        self.RemovePrimaryBranchesButton.clicked.connect(
            self.removePrimaryBranchesPressed)
        self.showConfirmedPrimaryBranchesCheck.toggled.connect(
            self.showConfirmedPrimaryBranchesChecked)
        self.showOnlyBranchesOfCurrentPrimaryNodeCheck.toggled.connect(
            self.showOnlyBranchesOfCurrentPrimaryNodeChecked)
        self.showTraitsOnlyCheck.toggled.connect(self.showTraitsOnlyChecked)

        self.SelectSegmentPointButton.clicked.connect(
            self.SelectSegmentPointPressed)
        self.ConfirmSegmentPointButton.clicked.connect(
            self.ConfirmSegmentPointPressed)
        self.showSelectedSegmentCheck.toggled.connect(
            self.showSelectedSegmentChecked)

        self.horizontalSliderRadius.sliderReleased.connect(
            self.horizontalSliderRadiusChanged)
        self.horizontalSliderRadius.setSliderDown(True)
        self.horizontalSliderRadius.setValue(10)
        self.horizontalSliderRadius.setSliderDown(False)

    def changeMode(self, mode: int):
        if self.mode != mode or mode < 6:
            self.mode = mode
            print("changing mode")
            if self.graph != None:
                self.graph.unselectAll()
                self.updateWidget()
                if mode != ConnectionMode:
                    self.graph.setDisplayOnlySelectedComponents(False)
                    self.graph.setShowBoundingBoxes(False)
                if mode == ConnectionMode:
                    self.graph.setDisplayOnlySelectedComponents(
                        self.showSelected)
                    self.graph.setShowBoundingBoxes(self.showBoxes)
                if mode != ConnectionMode and mode != SplitEdgeMode \
                        and mode != BreakMode \
                        and mode != RemoveComponentMode:
                    self.graph.setDisplayStem(self.showStem)
                    self.graph.setDisplayPrimaryNodes(self.showPrimaryNodes)
                else:
                    self.graph.setDisplayStem(False)
                    self.graph.setDisplayPrimaryNodes(False)
            self.modeChangeSig.emit(self.mode)

    def exitCurrentMode(self):
        pass

    def setGraph(self, graph: mgraph):
        print("setting graph")
        self.graph = graph
        self.updateWidget()
        if self.graph != None:
            if self.mode != ConnectionMode and self.mode != SplitEdgeMode \
                    and self.mode != BreakMode \
                    and self.mode != RemoveComponentMode:
                self.graph.setDisplayStem(self.showStem)
                self.graph.setDisplayPrimaryNodes(self.showPrimaryNodes)
            else:
                self.graph.setDisplayStem(False)
                self.graph.setDisplayPrimaryNodes(False)

    def headerData(self, section, orientation, role=Qt.DisplayRole):
        if role == Qt.DisplayRole and orientation == Qt.Horizontal:
            return self.header_labels[section]
        return QAbstractTableModel.headerData(self, section, orientation, role)

    def updateWidget(self):
        if self.graph != None:
            # update current primary node combo
            self.CurrentPrimaryNodeCombo.currentIndexChanged.disconnect(
                self.currentPrimaryNodeChanged)
            self.CurrentPrimaryNodeCombo.clear()
            numPrimaryNodes = self.graph.getNumPrimaryNodes()
            if numPrimaryNodes > 0:
                for i in range(0, numPrimaryNodes):
                    descriptor = str(i)
                    self.CurrentPrimaryNodeCombo.addItem(descriptor)
            else:
                self.CurrentPrimaryNodeCombo.clear()

            self.currentPrimaryNode = max(self.currentPrimaryNode, 0)
            self.currentPrimaryNode = min(self.currentPrimaryNode,
                                          numPrimaryNodes - 1)

            self.CurrentPrimaryNodeCombo.setCurrentIndex(
                self.currentPrimaryNode)
            self.graph.setCurrentPrimaryNode(self.currentPrimaryNode)
            self.CurrentPrimaryNodeCombo.currentIndexChanged.connect(
                self.currentPrimaryNodeChanged)

        else:
            self.CurrentPrimaryNodeCombo.clear()
class Video_Player(QtCore.QObject):
    pix = pyqtSignal(QPixmap)
    frame = pyqtSignal(np.ndarray)

    def __init__(self, dir, method,roi,method2, parent=None):
        super(Video_Player, self).__init__()
        self.video = cv2.VideoCapture(dir)
        self.last_frame = np.zeros((1, 1))
        self.num_frames = int(self.video.get(cv2.CAP_PROP_FRAME_COUNT))
        self.localize_method = method
        self.exctact_method = method2
        self.roi = roi
        self.count = 0
        self.stopped = True
        self.x = 0
        self.y = 0
        self.width = 0
        self.height = 0
        self.flag = 0
        self.tracker = Tracker.Tracker(20)
        self.syntactic = Syntactic_analysator.Syntatic_analysator()
        filename = 'finalized_model6.sav'
        filename3 = 'ocr_model.sav'
        self.loaded_model = pickle.load(open(filename, 'rb'))
        self.ocr = pickle.load(open(filename3, 'rb'))
        print(self.loaded_model)
        self.class_names = [ '0', '1', '2', '3', '4', '5', '6', '7', '8',
                   '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
                   'L', 'M', 'N', 'O', 'P', 'R','S', 'T',  'U', 'V', 'W',
                   'X', 'Y', 'Z','']



    def get_frame(self):
        sift = cv2.xfeatures2d.SIFT_create()
        fps = FPS().start()
        if self.localize_method == 3:
            self.Detector = Localize2.DetectorMorph()
        if self.localize_method == 2:
            self.Detector = Localize1.DetectorEdges()
        if self.localize_method == 1:
            self.Detector = Localize3.DetectorMorph()
        if self.exctact_method == 1:
            self.Extractor = Segmentation_chars.Segmentation()
        if self.exctact_method == 2:
            self.Extractor = Segmentation_chars2.Segmentation2()

        j = 0
        while self.video.get(cv2.CAP_PROP_POS_FRAMES) < self.num_frames and self.stopped == False:
            #flag when plate is found on frame
            found = 0
            ret, self.last_frame  = self.video.read()
            frame = cv2.resize(self.last_frame, None, fx=0.5, fy=0.5)
            tablice = []
            numbers = []
            if self.roi == 1:
                frame_roi = frame[int(self.y*(frame.shape[0]/400)):int(self.y*(frame.shape[0]/400)+self.height*(frame.shape[0]/400)), int(self.x*(frame.shape[1]/630)):int(self.x*(frame.shape[1]/630)+self.width*(frame.shape[1]/630))]
                candidates,xy_candidates = self.Detector.find_plate(frame_roi)
            if self.roi == 2:
                candidates, xy_candidates = self.Detector.find_plate(frame)

            for i in range(0,len(candidates)):

                cand = self.last_frame[(xy_candidates[i][0]+int(self.y*(frame.shape[0]/400))) * 2: (xy_candidates[i][2]+int(self.y*(frame.shape[0]/400))) * 2,
                       (xy_candidates[i][1]+int(self.x*(frame.shape[1]/630))) * 2: (xy_candidates[i][3]+int(self.x*(frame.shape[1]/630))) * 2]

                if self.flag==1:
                    j = j + 1
                    filename = "tab/222_" + "%s.png" % str(j)
                    cv2.imwrite(filename,cand)

                result = self.find_plate(cand)
                number = ""

                if result == 0:
                    tablice.append(xy_candidates[i])
                    chars = self.Extractor.segment(cand)
                    plate_numbers = []
                    for char in chars:
                        plate_numbers.append(self.predict_character(char))

                    plate_numbers, ok = self.syntactic.check(plate_numbers)

                    for char in plate_numbers:
                        if ok == 2:

                            number = number + self.class_names[char]
                    numbers.append(number)
                    cv2.rectangle(frame, (xy_candidates[i][1]+int(self.x*(frame.shape[1]/630)), xy_candidates[i][0]+int(self.y*(frame.shape[0]/400))),
                                      (xy_candidates[i][3]+int(self.x*(frame.shape[1]/630)), xy_candidates[i][2]+int(self.y*(frame.shape[0]/400))), (0, 255, 0), 2)

            obiekt, text, rect,missing = self.tracker.update(tablice,numbers)

            szukaj = []
            miss = []
            for im in missing:
                if isinstance(im[0], list):

                    mis = self.last_frame2[(im[0][0] + int(self.y * (frame.shape[0] / 400))) * 2: (im[0][2] + int(self.y * (frame.shape[0] / 400))) * 2,(im[0][1] + int(self.x * (frame.shape[1] / 630))) * 2: (im[0][3] + int(self.x * (frame.shape[1] / 630))) * 2]


                    x0 = (im[0][1] + int(self.x * (frame.shape[1] / 630))) *2 - 20
                    x1 = (im[0][3] + int(self.x * (frame.shape[1] / 630))) * 2 + 20
                    y0 = (im[0][0] + int(self.y * (frame.shape[0] / 400))) * 2 - 10
                    y1 = (im[0][2] + int(self.y * (frame.shape[0] / 400))) * 2 + 10

                    if x0 < 0:
                        x0 = 0
                    if x1 > self.last_frame.shape[1]:
                        x1 = self.last_frame.shape[1]
                    if y0 < 0 :
                        y0 = 0
                    if y1 > self.last_frame.shape[0]:
                        y1 = self.last_frame.shape[0]
                    szukaj.append([self.last_frame[y0:y1,x0: x1],[x0,y0,x1,y1]])
                    miss.append(mis)
                else:

                    mis = self.last_frame2[(im[0] + int(self.y * (frame.shape[0] / 400))) * 2: (im[2] + int(
                        self.y * (frame.shape[0] / 400))) * 2, (im[1] + int(self.x * (frame.shape[1] / 630))) * 2: (im[
                                                                                                                        3] + int(
                        self.x * (frame.shape[1] / 630))) * 2]
                    miss.append(mis)

                    x0 = (im[1] + int(self.x * (frame.shape[1] / 630))) * 2 - 30
                    x1 = (im[3] + int(self.x * (frame.shape[1] / 630))) * 2 + 30
                    y0 = (im[0] + int(self.y * (frame.shape[0] / 400))) * 2 - 15
                    y1 = (im[2] + int(self.y * (frame.shape[0] / 400))) * 2 + 15

                    if x0 < 0:
                        x0 = 0
                    if x1 > self.last_frame.shape[1]:
                        x1 = self.last_frame.shape[1]
                    if y0 < 0 :
                        y0 = 0
                    if y1 > self.last_frame.shape[0]:
                        y1 = self.last_frame.shape[0]
                    szukaj.append([self.last_frame[y0:y1,x0: x1],[x0,y0,x1,y1]])


                #cv2.waitKey(0)
            finded = []
            for mis in range(0,len(miss)):
                FLANN_INDEX_KDITREE = 0
                MIN_MATCH_COUNT = 20
                flannParam = dict(algorithm=FLANN_INDEX_KDITREE, tree=5)
                flann = cv2.FlannBasedMatcher(flannParam, {})
                missa = cv2.cvtColor(miss[mis], cv2.COLOR_BGR2GRAY)
                szukaja = cv2.cvtColor(szukaj[mis][0], cv2.COLOR_BGR2GRAY)

                trainKP, trainDesc = sift.detectAndCompute(missa, None)

                queryKP, queryDesc = sift.detectAndCompute(szukaja, None)

                try:
                    if (type(queryDesc) != 'NoneType') or (type(trainDesc) != 'NoneType') :
                        matches = flann.knnMatch(queryDesc, trainDesc, k=2)

                        goodMatch = []
                        for m, n in matches:
                            if (m.distance < 0.75 * n.distance):
                                goodMatch.append(m)
                        if (len(goodMatch) > MIN_MATCH_COUNT):
                            tp = []
                            qp = []
                            for m in goodMatch:
                                tp.append(trainKP[m.trainIdx].pt)
                                qp.append(queryKP[m.queryIdx].pt)

                            tp, qp = np.float32((tp, qp))

                            H, status = cv2.findHomography(tp, qp, cv2.RANSAC, 3.0)
                            h, w = missa.shape

                            trainBorder = np.float32([[[0, 0], [0, h - 1], [w - 1, h - 1], [w - 1, 0]]])

                            queryBorder = cv2.perspectiveTransform(trainBorder, H)
                            xy = [int(queryBorder[0][0][0]), int(queryBorder[0][0][1]), int(queryBorder[0][2][0]),
                            int(queryBorder[0][2][1])]
                            tabb = [szukaj[mis][1][0]+xy[0],szukaj[mis][1][0]+xy[2],szukaj[mis][1][1]+xy[1],szukaj[mis][1][1]+xy[3]]
                            finded.append(tabb)

                        else:
                            print("Not Enough match found- %d/%d" % (len(goodMatch), MIN_MATCH_COUNT))
                except:
                    pass

            for find in finded:
                if find[0]<0:
                    find[0] = 0
                if find[1]<0:
                    find[1] = 0
                if find[2]<0:
                    find[2] = 0
                if find[3]<0:
                    find[3] = 0

                if find[0]>self.last_frame.shape[1]:
                    find[0] = self.last_frame.shape[1]
                if find[1]>self.last_frame.shape[1]:
                    find[1] = self.last_frame.shape[1]
                if find[2]>self.last_frame.shape[0]:
                    find[2] = self.last_frame.shape[0]
                if find[3]>self.last_frame.shape[0]:
                    find[3] = self.last_frame.shape[0]

                if find[2]>find[3]:
                    temp = find[2]
                    find[2]=find[3]
                    find[3]=temp
                if find[0]>find[1]:
                    temp = find[0]
                    find[0]=find[1]
                    find[1]=temp

                if find[2] == find[3]:
                    find[2]=0
                if find[0] == find[1]:
                    find[0]=0
                print(find[0])
                print(find[1])
                print(find[2])
                print(find[3])
                cand = self.last_frame[find[2] : find[3],find[0]: find[1] ]
                chars = self.Extractor.segment(cand)
                plate_numbers = []
                number = ""
                for char in chars:
                    plate_numbers.append(self.predict_character(char))
                plate_numbers, ok = self.syntactic.check(plate_numbers)

                for char in plate_numbers:
                    if ok == 2:

                       number = number + self.class_names[char]

                if len(number) >= 2:
                    found = 1
                    numbers.append(number)
                    tablice.append([int((find[2]-int(self.y*(frame.shape[0]/400)))/2),int((find[0]-int(self.x*(frame.shape[1]/630))) / 2),int((find[3]-int(self.y*(frame.shape[0]/400)))/2),int((find[1]-int(self.x*(frame.shape[1]/630))) / 2)])

            if found == 1:
                obiekt, text, rect, missing = self.tracker.update(tablice, numbers)

            for (objectID, centroid) in obiekt.items():

                txt = "{}".format(text.get(objectID))
                cv2.putText(frame, txt, (centroid[0]+int(self.x*(frame.shape[1]/630)) - 10, centroid[1]+int(self.y*(frame.shape[0]/400)) - 10),
                            cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1)

            rgbImage = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
            h, w, ch = rgbImage.shape

            bytesPerLine = ch * w
            p = QtGui.QImage(rgbImage.data, w, h, bytesPerLine, QtGui.QImage.Format_RGB888)
            p = QPixmap.fromImage(p)
            pixmap = p.scaled(630, 400)

            self.pix.emit(pixmap)
            fps.update()
            self.last_frame2 = self.last_frame

        fps.stop()
        print("[INFO] elasped time: {:.2f}".format(fps.elapsed()))
        print("[INFO] approx. FPS: {:.2f}".format(fps.fps()))

    def set_frame(self):
        ret, self.last_frame = self.video.read()
        frame = cv2.resize(self.last_frame, None, fx=0.4, fy=0.4)
        rgbImage = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        h, w, ch = rgbImage.shape
        bytesPerLine = ch * w
        p = QtGui.QImage(rgbImage.data, w, h, bytesPerLine, QtGui.QImage.Format_RGB888)
        p = QPixmap.fromImage(p)
        pixmap = p.scaled(630, 400)

        self.pix.emit(pixmap)

        self.video.set(cv2.CAP_PROP_POS_AVI_RATIO,0)

    def change_settings(self,localize_metgod, ekstract_method, roi):
        self.localize_method = localize_metgod

        self.exctact_method = ekstract_method

        self.roi = roi

    def apply(self):
        if self.localize_method == 1:
            self.Detector = Localize2.DetectorMorph()
        if self.localize_method == 2:
            self.Detector = Localize1.DetectorEdges()
        if self.localize_method == 3:
            self.Detector = Localize3.DetectorMorph()
        if self.exctact_method == 1:
            self.Extractor = Segmentation_chars.Segmentation()
        if self.exctact_method == 2:
            self.Extractor = Segmentation_chars2.Segmentation2()

    def set_roi(self,x,y,h,w):
        if self.roi ==1:
            self.x = x
            self.y = y
            self.height = h
            self.width = w

    def pre_process(self,frame):
        height, width, numChannels = frame.shape

        imgHSV = np.zeros((height, width, 3), np.uint8)
        imgHSV = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
        imgHue, imgSaturation, imgGrayscale = cv2.split(imgHSV)

        imgTopHat = np.zeros((height, width, 1), np.uint8)
        imgBlackHat = np.zeros((height, width, 1), np.uint8)
        structuringElement = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
        imgTopHat = cv2.morphologyEx(imgGrayscale, cv2.MORPH_TOPHAT, structuringElement)
        imgBlackHat = cv2.morphologyEx(imgGrayscale, cv2.MORPH_BLACKHAT, structuringElement)
        imgGrayscalePlusTopHat = cv2.add(imgGrayscale, imgTopHat)
        imgMaxContrastGrayscale = cv2.subtract(imgGrayscalePlusTopHat, imgBlackHat)
        imgBlurred = np.zeros((height, width, 1), np.uint8)
        imgBlurred = cv2.GaussianBlur(imgMaxContrastGrayscale, (3, 3), 0)

        kernel_ver = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]])
        vertical = cv2.filter2D(imgBlurred, -1, kernel_ver)

        th4 = cv2.threshold(vertical, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]

        kernel_sver = np.array([[0, 1, 0], [0, 1, 0], [0, 1, 0]])
        th4 = cv2.filter2D(th4, -1, kernel_sver)

        return th4,vertical

    def filter_contour(self,threshold):
        cnts = cv2.findContours(threshold, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        cnts = imutils.grab_contours(cnts)
        orig = threshold.copy()
        for c in cnts:
            peri = cv2.arcLength(c, True)
            if peri <= 10 or peri >= 45:
                cv2.drawContours(orig, [c], -1, (0, 255, 0), 2)

        img2 = np.zeros(orig.shape, np.uint8)
        kernel2 = np.ones((2, 2), np.uint8)
        for i in range(1, 15):
            orig2 = orig.copy()
            kernel = np.ones((i, i), np.uint8)
            orig2 = cv2.dilate(orig2, kernel2, iterations=1)
            orig2 = cv2.morphologyEx(orig2, cv2.MORPH_CLOSE, kernel)
            orig2 = cv2.morphologyEx(orig2, cv2.MORPH_OPEN, kernel)
            nlabel, labels, stats, centroids = cv2.connectedComponentsWithStats(orig2, connectivity=8)
            for el in range(1, nlabel):
                if stats[el][2] / stats[el][3] > 3.5 and stats[el][2] / stats[el][3] < 5.5 and stats[el][3] > 10 and stats[el][3] < 40:
                    img2[labels == el] = int(255)
        return img2


    def predict_character(self,character):
        character=character.flatten()
        char = self.ocr.predict(character.reshape(1, -1))[0]
        return char


    def find_plate(self,plate):

        gray = cv2.cvtColor(plate, cv2.COLOR_BGR2GRAY)
        logo = cv2.resize(gray, (120, 30))
        H = feature.hog(logo, orientations=9, pixels_per_cell=(6, 6),
                                    cells_per_block=(3, 3), transform_sqrt=True, block_norm="L1")
        result = self.loaded_model.predict(H.reshape(1, -1))[0]
        return result

    def save_frame(self):

        filename = "posittt/nadasie/tab15_"+"%s.bmp" % str(self.count)
        cv2.imwrite(filename, self.last_frame)
        self.count = self.count + 1

    def __del__(self):
        self.video.release()
Пример #33
0
    def __init__(self):
        super(PlayControl, self).__init__()
        self.__tbnSize = 20

        self.tbn_play_pause = QToolButton()
        self.tbn_play_pause.setFixedSize(self.__tbnSize, self.__tbnSize)
        self.tbn_play_pause.setShortcut(Qt.Key_Space)
        self.tbn_play_pause.setObjectName('tbn_play_pause')

        self.tbn_play_next = QToolButton()
        self.tbn_play_next.setFixedSize(self.__tbnSize, self.__tbnSize)
        self.tbn_play_next.setObjectName('tbnPlayNext')

        self.tbn_play_previous = QToolButton()
        self.tbn_play_previous.setFixedSize(self.__tbnSize, self.__tbnSize)
        self.tbn_play_previous.setObjectName('tbn_play_previous')

        self.__tbnPlayVolume = QToolButton()
        self.__tbnPlayVolume.setFixedSize(self.__tbnSize, self.__tbnSize)
        self.__tbnPlayVolume.setObjectName('tbnPlayVolume')

        self.__playProcess = QSlider(Qt.Horizontal)
        self.__playProcess.setFixedHeight(10)
#        self.__playProcess.setStyleSheet('border-image: url(Images/play_pause);')

        self.__tbnPlayMode = QToolButton()
        self.__tbnPlayMode.setFixedSize(self.__tbnSize, self.__tbnSize)
        self.__tbnPlayMode.setObjectName('tbnPlayMode')

        self.__layout = QHBoxLayout()
        self.__layout.addWidget(self.tbn_play_previous)
        self.__layout.addWidget(self.tbn_play_pause)
        self.__layout.addWidget(self.tbn_play_next)
        self.__layout.addWidget(self.__tbnPlayVolume)
        self.__layout.addWidget(self.__tbnPlayMode)

        self.__layoutTop = QVBoxLayout()
        self.__layoutTop.addWidget(self.__playProcess)
        self.__layoutTop.addLayout(self.__layout)
        self.setLayout(self.__layoutTop)

        self.setStyleSheet('''
            #tbn_play_pause{border-image: url(Images/play_pause);}
            #tbn_play_pause:hover{border-image: url(Images/play_pause_hover);}
            #tbn_play_previous{border-image: url(Images/play_previous);}
            #tbn_play_previous:hover{border-image: url(Images/play_previous_hover);}
            #tbnPlayNext{border-image: url(Images/play_next);}
            #tbnPlayNext:hover{border-image: url(Images/play_next_hover);}
            #tbnPlayVolume{border-image: url(Images/volume);}
            #tbnPlayVolume:hover{border-image: url(Images/volume_hover);}
            #tbnPlayMode{border-image: url(Images/playModel_sequence);}
            #tbnPlayMode:hover{border-image: url(Images/playModel_sequence_hover);}
        ''')

#        self.connect(self.tbn_play_pause, QtCore.SIGNAL('clicked()'), QtCore.SIGNAL('PlayPause()'))
#        self.connect(self.tbn_play_next, QtCore.SIGNAL('clicked()'), QtCore.SIGNAL('PlayNext()'))
#        self.connect(self.tbn_play_previous, QtCore.SIGNAL('clicked()'), QtCore.SIGNAL('PlayPrevious()'))
#        self.connect(self.__tbnPlayVolume, QtCore.SIGNAL('clicked()'), QtCore.SIGNAL('PlayVolume()'))
#        self.connect(self.__playProcess, QtCore.SIGNAL('valueChanged(int)'), QtCore.SIGNAL('PlayValueChanged(int)'))
#        self.connect(self.__tbnPlayMode, QtCore.SIGNAL('clicked()'), QtCore.SIGNAL('PlayMode()'))

        self.PlayPause = pyqtSignal()
        self.PlayNext = pyqtSignal()
        self.PlayPrevious = pyqtSignal()
        self.PlayVolume = pyqtSignal()
        self.PlayValueChanged = pyqtSignal()
        self.PlayMode = pyqtSignal()
#        self.tbn_play_pause.clicked.connect(self.parentWidget.parentWidget().close)
        self.tbn_play_next.clicked.connect(self.PlayNext)
        self.tbn_play_previous.clicked.connect(self.PlayPrevious)
        self.__tbnPlayVolume.clicked.connect(self.PlayVolume)
        self.__playProcess.valueChanged.connect(self.PlayValueChanged)
        self.__tbnPlayMode.clicked.connect(self.PlayMode)
Пример #34
0
class SIRTrackerSignals(QObject):
    status_changed = pyqtSignal(int, str)
    frame_changed = pyqtSignal(dict)
    template_changed = pyqtSignal(object)
    finished = pyqtSignal(int)
Пример #35
0
class Browser(QtWebKitWidgets.QWebView):

    gotHtmlSignal = pyqtSignal(str, str, str)

    def __init__(self, ui, home, screen_width, quality, site, epnArrList):
        super(Browser, self).__init__()
        self.setPage(BrowserPage())
        #self.page().setLinkDelegationPolicy(QWebPage.DelegateAllLinks)
        self.hdr = 'Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:45.0) Gecko/20100101 Firefox/45.0'
        self.img_url = ''
        self.ui = ui
        self.quality = quality
        self.site = site
        self.home = home
        self.epnArrList = epnArrList
        self.wait_player = False
        self.urlChanged.connect(self.url_changed)
        self.hoveredLink = ''
        self.media_url = ''
        self.epn_name_in_list = ''
        #self.loadFinished.connect(self._load_finished)
        #self.loadStarted.connect(self._load_started)
        self.titleChanged.connect(self.title_changed)
        self.loadProgress.connect(self.load_progress)
        self.current_link = ''
        self.title_page = ''
        #ui.tab_2.showMaximized()
        self.ui.tab_2.setMaximumWidth(screen_width)
        self.url_arr = []
        self.timer = QtCore.QTimer()
        self.timer.timeout.connect(self.player_wait)
        self.timer.setSingleShot(True)
        #self.linkClicked.connect(self.link_clicked)
        self.hit_link = ''
        self.playlist_dict = {}
        self.get_playlist = False
        self.playlist_name = ''
        self.gotHtmlSignal.connect(self.got_curl_html)
        self.yt_sub_folder = os.path.join(home, 'External-Subtitle')
        if not os.path.exists(self.yt_sub_folder):
            os.makedirs(self.yt_sub_folder)

    def link_clicked(self, link):
        print('--link--clicked--')
        self.current_link = link.toString()
        print('--link--clicked--', self.current_link)
        m = []
        if '/watch?' in link.toString():
            a = link.toString().split('?')[-1]
            b = a.split('&')
            if b:
                for i in b:
                    j = i.split('=')
                    k = (j[0], j[1])
                    m.append(k)
            else:
                j = a.split('=')
                k = (j[0], j[1])
                m.append(k)
            d = dict(m)
            print(d, '----dict--arguments---generated---')
            try:
                self.current_link = 'https://m.youtube.com/watch?v=' + d['v']
            except:
                pass
        if ((self.current_link.startswith("https://m.youtube.com/watch?v=") or
             self.current_link.startswith("https://www.youtube.com/watch?v="))
                and not self.wait_player):
            #self.page().mainFrame().evaluateJavaScript("var element = document.getElementById('player');element.innerHtml='';")
            self.page().mainFrame().evaluateJavaScript(
                "var element = document.getElementById('player');element.parentNode.removeChild(element);"
            )
            self.wait_player = True
            self.clicked_link(self.current_link)
            #QtCore.QTimer.singleShot(1, partial(self.clicked_link,self.current_link))
            self.timer.start(1000)

    def player_wait(self):
        self.wait_player = False
        self.page().mainFrame().evaluateJavaScript("location.reload();")

    def get_html(self, var):
        print('--got--html--', self.url().toString())
        if 'youtube' in self.url().toString():
            self.playlist_dict = {}
            x = urllib.parse.unquote(var)
            x = x.replace('\\\\u0026', '&')
            l = re.findall('url=https[^"]*', x)
            for i in l:
                if self.ui.quality_val == 'sd':
                    if 'itag=18' in i:
                        final_url = re.sub('url=', '', i)

            soup = BeautifulSoup(var, 'lxml')
            m = soup.find('div', {'id': 'player'})

            if m:
                print('removing')
                #self.page().mainFrame().evaluateJavaScript("var element = document.getElementById('player');element.parentNode.removeChild(element);")
                self.page().mainFrame().evaluateJavaScript(
                    "var element = document.getElementById('player');element.innerHtml='';"
                )
            title = soup.find('title')
            if title:
                if (self.current_link.startswith(
                        "https://m.youtube.com/watch?v=")
                        or self.current_link.startswith(
                            "https://www.youtube.com/watch?v=")):
                    self.epn_name_in_list = title.text
                    self.ui.epn_name_in_list = title.text
                    print(title.text,
                          self.url().toString(), '--changed-title--')

            if ('list=' in self.url().toString()
                    and 'www.youtube.com' in self.url().toString()):
                ut_c = soup.findAll(
                    'li',
                    {'class': "yt-uix-scroller-scroll-unit currently-playing"})
                ut = soup.findAll('li',
                                  {'class': "yt-uix-scroller-scroll-unit "})
                if ut_c:
                    ut = ut_c + ut
                print(ut)
                arr = []
                for i in ut:
                    try:
                        j1 = i['data-video-id'] + '#' + i['data-video-title']
                        print(j1)
                        j = i['data-video-id']
                        k = i['data-video-title']
                        l = (j, k)
                        arr.append(l)
                    except:
                        pass
                d = dict(arr)
                print(d)
                print(arr)
                if d:
                    self.playlist_dict = d
            elif 'list=' in self.url().toString():
                o = soup.find('div', {'id': 'content-container'})
                if o:
                    m = o.findAll('img')
                else:
                    m = []
                n = []
                d = {}
                for i in m:
                    #print(i['src'])
                    try:
                        g = i.find_next('h4')
                        yt_id = i['src'].split('/')[-2]
                        n.append((yt_id, g.text))
                    except:
                        pass
                if n:
                    d = dict(n)
                print(d)
                if d:
                    self.playlist_dict = d

    def load_progress(self, var):
        if var == 100 and 'youtube.com' in self.url().toString():
            print(self.url().toString(), self.title(), '--load--progress--')
            frame = self.page().mainFrame().toHtml()
            self.get_html(frame)

    def title_changed(self, title):
        a = 0
        print(title, self.url().toString(), '--title--change--')
        #self.page().mainFrame().evaluateJavaScript("location.reload();")
        self.ui.epn_name_in_list = title

    def url_changed(self, link):
        print('\n--url_changed--\n', link.url(), '\n--url_changed--\n')
        if not self.url_arr:
            self.url_arr.append(link.url())
            prev_url = ''
        else:
            prev_url = self.url_arr[-1]
            self.url_arr.append(link.url())

        if prev_url != link.url() and 'youtube' in link.url():
            self.current_link = link.url()
            m = []
            if '/watch?' in link.url():
                a = link.url().split('?')[-1]
                b = a.split('&')
                if b:
                    for i in b:
                        j = i.split('=')
                        k = (j[0], j[1])
                        m.append(k)
                else:
                    j = a.split('=')
                    k = (j[0], j[1])
                    m.append(k)
                d = dict(m)
                print(d, '----dict--arguments---generated---')
                try:
                    self.current_link = 'https://m.youtube.com/watch?v=' + d[
                        'v']
                except:
                    pass
            if ((self.current_link.startswith("https://m.youtube.com/watch?v=")
                 or self.current_link.startswith(
                     "https://www.youtube.com/watch?v="))
                    and not self.wait_player):
                self.wait_player = True
                self.clicked_link(self.current_link)
                self.timer.start(1000)

        print(self.url_arr)

    def clicked_link(self, link):
        final_url = ''
        url = link
        self.epn_name_in_list = self.title()
        print(url, 'clicked_link')
        if 'youtube.com/watch?v=' in url:
            if self.ui.mpvplayer_val.processId() > 0:
                self.ui.mpvplayer_val.kill()
                self.ui.mpvplayer_started = False
            self.ui.get_final_link(url, self.ui.quality_val, self.ui.ytdl_path,
                                   self.ui.logger, self.epn_name_in_list,
                                   self.hdr)

    def custom_links(self, q_url):
        url = q_url
        self.hoveredLink = url

    def keyPressEvent(self, event):
        if event.modifiers() == QtCore.Qt.AltModifier and event.key(
        ) == QtCore.Qt.Key_Left:
            self.back()
        elif event.modifiers() == QtCore.Qt.AltModifier and event.key(
        ) == QtCore.Qt.Key_Right:
            self.forward()
        super(Browser, self).keyPressEvent(event)

    @pyqtSlot(str, str, str)
    def got_curl_html(self, title, url, value):
        file_path = os.path.join(self.home, 'Playlists', str(value))
        if '/' in title:
            title = title.replace('/', '-')
        t = title + '	' + url + '	' + 'NONE'
        write_files(file_path, t, line_by_line=True)
        self.ui.update_playlist(file_path)

    def add_playlist(self, value):
        value = value.replace('/', '-')
        value = value.replace('#', '')
        if value.startswith('.'):
            value = value[1:]
        file_path = os.path.join(self.home, 'Playlists', str(value))
        new_pl = False
        j = 0
        new_arr = []
        for i in self.playlist_dict:
            yt_id = i
            title = self.playlist_dict[yt_id]
            title = title.replace('/', '-')
            title = title.replace('#', '')
            if title.startswith('.'):
                title = title[1:]
            n_url = 'https://m.youtube.com/watch?v=' + yt_id
            w = title + '	' + n_url + '	' + 'NONE'
            new_arr.append(w)
            j = j + 1
        write_files(file_path, new_arr, line_by_line=True)
        self.get_playlist = False

    def triggerPlaylist(self, value, url, title):
        print('Menu Clicked')
        print(value)
        file_path = os.path.join(self.home, 'Playlists', str(value))
        if 'ytimg.com' in url:
            try:
                print(self.playlist_dict)
                yt_id = url.split('/')[-2]
                url = 'https://m.youtube.com/watch?v=' + yt_id
                title = self.playlist_dict[yt_id]
            except:
                pass
        if '/' in title:
            title = title.replace('/', '-')
        if '#' in title:
            title = title.replace('#', '')
        if title.startswith('.'):
            title = title[1:]
        if 'list=' in url:
            title = title + '-Playlist'
        img_u = ''
        if self.img_url:
            img_u = self.img_url.toString()
        if 'playlist?list=' in url and img_u:
            try:
                yt_id = img_u.split('/')[-2]
                o_url = r'https://m.youtube.com/playlist?list='
                n_url = 'https://m.youtube.com/watch?v=' + yt_id + '&index=1&list='
                url = url.replace(o_url, n_url)
                print(url, o_url, n_url)
            except:
                pass
        print(title, url, file_path)
        t = title + '	' + url + '	' + 'NONE'
        write_files(file_path, t, line_by_line=True)
        self.ui.update_playlist(file_path)

    def contextMenuEvent(self, event):
        self.img_url = ''
        menu = self.page().createStandardContextMenu()
        hit = self.page().currentFrame().hitTestContent(event.pos())
        hit_m = self.page().mainFrame()
        hit_n = hit_m.hitTestContent(event.pos())
        url = hit.linkUrl()
        arr = ['Download As Fanart', 'Download As Cover']
        arr_extra_tvdb = ['Series Link', 'Season Episode Link']
        arr_last = ['Artist Link']
        action = []
        self.img_url = hit.imageUrl()
        self.title_page = hit.linkText()
        yt = False
        try:
            if self.title_page:
                print('self.title_page=', self.title_page)
                #self.title_page = self.title_page.strip()
                if 'youtube.com' in self.url().toString():
                    self.title_page = hit_n.linkElement().toPlainText()
                if not self.title_page:
                    self.title_page = hit.linkText()
                self.title_page = self.title_page.strip()
                tmp = self.title_page.replace('\n', '#')
                print(tmp)
                tmp1 = re.search('#[^#]*', tmp)
                print(tmp1)
                self.title_page = tmp1.group()
                self.title_page = self.title_page.replace('#', '')

            else:
                self.title_page = hit.title()
        except:
            self.title_page = hit.title()

        print('url--info\n', self.img_url.toString(), '=img_url\n', url,
              '=url', hit.title(), '=title\n', hit.linkText(), '=linktext\n',
              hit_m.title(), '--p_title--',
              hit_n.linkElement().toPlainText(), '--link-element')

        if (url.isEmpty()
                or not url.toString().startswith('http')) and self.img_url:
            url = self.img_url
        if url.isEmpty():
            url = self.url()
            print('--next--url=', self.url().toString())
            self.title_page = hit_m.title()
            if 'reload' in self.url().toString():
                print('reload # in url')
                url = self.url().toString()
                n_url = re.sub('\?reload[^\/]*\/|&mode=NORMAL|&params[^&]*',
                               '', url)
                url = QUrl(n_url)
            print('--next--url=', url.toString())
        if not url.isEmpty() or self.img_url:
            if 'tvdb' in url.toString():
                arr = arr + arr_extra_tvdb
            if 'last.fm' in url.toString():
                arr = arr + arr_last
            if 'youtube.com' in url.toString() or 'ytimg.com' in url.toString(
            ):
                yt = True
                arr[:] = []
                arr.append('Play with Kawaii-Player')
                arr.append('Queue Item')
                arr.append('Download')
                arr.append('Get Subtitle (If Available)')
                if 'ytimg.com' in url.toString():
                    print(self.playlist_dict)
                    yt_id = url.toString().split('/')[-2]
                    url = QUrl('https://m.youtube.com/watch?v=' + yt_id)
                    print('url=', url)
                    try:
                        self.title_page = self.playlist_dict[yt_id]
                    except:
                        self.title_page = ''
                    arr.append('Add as Local Playlist')
                    self.playlist_name = self.epn_name_in_list

                menu.addSeparator()
                submenuR = QtWidgets.QMenu(menu)
                submenuR.setTitle("Add To Playlist")
                menu.addMenu(submenuR)
                pls = os.listdir(os.path.join(self.home, 'Playlists'))
                home1 = os.path.join(self.home, 'Playlists')
                pls = sorted(
                    pls,
                    key=lambda x: os.path.getmtime(os.path.join(home1, x)),
                    reverse=True)
                item_m = []
                for i in pls:
                    item_m.append(submenuR.addAction(i))

                submenuR.addSeparator()
                new_pls = submenuR.addAction("Create New Playlist")

            for i in range(len(arr)):
                action.append(menu.addAction(arr[i]))

            act = menu.exec_(event.globalPos())
            for i in range(len(action)):
                if act == action[i]:
                    self.download(url, arr[i])
            if yt:
                for i in range(len(item_m)):
                    #print(hit.title(),self.title_page)
                    if act == item_m[i]:
                        if 'views' in self.title_page:
                            #content = ccurl(url.toString())
                            #soup = BeautifulSoup(content,'lxml')
                            self.title_page = re.sub('[0-9][^ ]* ', '',
                                                     self.title_page, 1)
                            self.title_page = re.sub('[0-9][^ ]* views', '',
                                                     self.title_page, 1)
                            self.title_page = self.title_page.replace('/', '-')
                            print('self.title_page=', self.title_page)
                        if not self.title_page:
                            content = ccurl(url.toString())
                            soup = BeautifulSoup(content, 'lxml')
                            self.title_page = soup.title.text.strip().replace(
                                '/', '-')
                            ##self.title_page = hit_m.title().strip().replace('/','-')
                            ##print(hit.title(),self.title_page)
                            #thr = downloadThread(url.toString(),self,pls[i])
                            #thr.start()
                        self.triggerPlaylist(pls[i], url.toString(),
                                             self.title_page)

                if act == new_pls:
                    print("creating")
                    MainWindow = QtWidgets.QWidget()
                    item, ok = QtWidgets.QInputDialog.getText(
                        MainWindow, 'Input Dialog', 'Enter Playlist Name')
                    if ok and item:
                        file_path = os.path.join(self.home, 'Playlists', item)
                        if not os.path.exists(file_path):
                            f = open(file_path, 'w')
                            f.close()

        super(Browser, self).contextMenuEvent(event)

    def ccurlT(self, url, rfr):
        content = ccurl(url)
        return content

    def download(self, url, option):
        if option.lower() == 'play with kawaii-player':
            final_url = ''
            self.ui.epn_name_in_list = self.title_page
            print(self.ui.epn_name_in_list)
            if self.ui.mpvplayer_val.processId() > 0:
                self.ui.mpvplayer_val.kill()
                self.ui.mpvplayer_started = False
            self.ui.get_final_link(url.toString(), self.ui.quality_val,
                                   self.ui.ytdl_path, self.ui.logger,
                                   self.ui.epn_name_in_list, self.hdr)
        elif option.lower() == 'add as local playlist':
            self.get_playlist = True
            if self.playlist_dict:
                print(self.get_playlist, '=get_playlist')
                self.add_playlist(self.playlist_name)
        elif option.lower() == 'download':
            if self.ui.quality_val == 'sd480p':
                txt = "Video can't be saved in 480p, Saving in either HD or SD"
                send_notification(txt)
                quality = 'hd'
            else:
                quality = self.ui.quality_val
            finalUrl = get_yt_url(url.toString(),
                                  quality,
                                  self.ui.ytdl_path,
                                  self.ui.logger,
                                  mode='offline')
            finalUrl = finalUrl.replace('\n', '')
            title = self.title_page + '.mp4'
            title = title.replace('"', '')
            title = title.replace('/', '-')
            if os.path.exists(self.ui.default_download_location):
                title = os.path.join(self.ui.default_download_location, title)
            else:
                title = os.path.join(self.ui.tmp_download_folder, title)
            command = wget_string(finalUrl, title, self.ui.get_fetch_library)
            print(command)
            self.ui.infoWget(command, 0)

        elif option.lower() == 'get subtitle (if available)':
            self.ui.epn_name_in_list = self.title_page
            print(self.ui.epn_name_in_list)
            get_yt_sub(url.toString(), self.ui.epn_name_in_list,
                       self.yt_sub_folder, self.ui.tmp_download_folder,
                       self.ui.ytdl_path, self.ui.logger)

        elif option.lower() == 'queue item':
            file_path = os.path.join(self.home, 'Playlists', 'Queue')
            if not os.path.exists(file_path):
                f = open(file_path, 'w')
                f.close()
            if not self.ui.queue_url_list:
                self.ui.list6.clear()
            title = self.title_page.replace('/', '-')
            if title.startswith('.'):
                title = title[1:]
            r = title + '	' + url.toString() + '	' + 'NONE'
            self.ui.queue_url_list.append(r)
            self.ui.list6.addItem(title)
            print(self.ui.queue_url_list)
            write_files(file_path, r, line_by_line=True)
        elif option.lower() == 'season episode link':
            if self.site != "Music" and self.site != "PlayLists":
                self.ui.getTvdbEpnInfo(url.toString())
        elif option.lower() == 'artist link' or option.lower(
        ) == 'series link':
            url = url.toString()
            r = self.ui.list1.currentRow()
            nm = self.ui.get_title_name(r)
            self.ui.posterfound_new(name=nm,
                                    site=self.site,
                                    url=url,
                                    direct_url=True,
                                    copy_summary=True,
                                    copy_poster=True,
                                    copy_fanart=True)
        else:
            url = url.toString()
            if url:
                t_content = ccurl(url + '#' + '-I')
                if 'image/jpeg' in t_content and not 'Location:' in t_content:
                    pass
                elif 'image/jpeg' in t_content and 'Location:' in t_content:
                    m = re.findall('Location: [^\n]*', t_content)
                    found = re.sub('Location: |\r', '', m[0])
                    url = found
                elif not self.img_url.isEmpty():
                    url = self.img_url.toString()
                else:
                    return 0

                if option.lower() == "download as fanart":
                    r = self.ui.list1.currentRow()
                    nm = self.ui.get_title_name(r)
                    print(option, '----')
                    self.ui.posterfound_new(name=nm,
                                            site=self.site,
                                            url=url,
                                            direct_url=True,
                                            copy_summary=False,
                                            copy_poster=False,
                                            copy_fanart=True)
                elif option.lower() == "download as cover":
                    r = self.ui.list1.currentRow()
                    nm = self.ui.get_title_name(r)
                    self.ui.posterfound_new(name=nm,
                                            site=self.site,
                                            url=url,
                                            direct_url=True,
                                            copy_summary=False,
                                            copy_poster=True,
                                            copy_fanart=False)
Пример #36
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.selected_item = None
        self.dialog = None
        self.downloads_request_mgr = TriblerRequestManager()
        self.request_mgr = None

    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 = "downloads?get_peers=1&get_pieces=1"

        self.downloads_request_mgr.generate_request_id()
        self.downloads_request_mgr.perform_request(url, self.on_received_downloads)

    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()
        for download in downloads["downloads"]:
            if download["infohash"] in self.download_widgets:
                item = self.download_widgets[download["infohash"]]
            else:
                item = DownloadWidgetItem(self.window().downloads_list)
                self.download_widgets[download["infohash"]] = 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()

        # Check whether there are download that should be removed
        toremove = set()
        for infohash, item in self.download_widgets.iteritems():
            if infohash not in download_infohashes:
                index = self.window().downloads_list.indexOfTopLevelItem(item)
                toremove.add((infohash, index))

        for infohash, index in toremove:
            self.window().downloads_list.takeTopLevelItem(index)
            del self.download_widgets[infohash]

        if QSystemTrayIcon.isSystemTrayAvailable():
            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()
            item.setHidden(
                not item.get_raw_download_status() in DOWNLOADS_FILTER_DEFINITION[self.filter] or not filter_match)

    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

        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["modified"]:
            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["modified"]:
            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["removed"]:
            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['modified']:
            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):
        QDesktopServices.openUrl(QUrl.fromLocalFile(self.selected_item.download_info["destination"]))

    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
            infohash = self.selected_item.download_info['infohash']
            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" % infohash)
            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 QSystemTrayIcon.isSystemTrayAvailable():
                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))
Пример #37
0
class MainWindow(QDialog, form_class):
    # signals:
    input_changed = pyqtSignal('QString')
    output_changed = pyqtSignal('QString')


    def __init__(self, *args):
        super(MainWindow, self).__init__(*args)

        # setting up ui
        self.setupUi(self)

        # other initializations
        self.dimensions = [self.x1_dim.value(), self.x2_dim.value(),
                                    self.x3_dim.value(), self.y_dim.value()]
        self.degrees = [self.x1_deg.value(), self.x2_deg.value(), self.x3_deg.value()]
        self.type = 'null'
        if self.radio_sh_cheb.isChecked():
            self.type = 'sh_cheb_doubled'
        elif self.radio_cheb.isChecked():
            self.type = 'cheb'
        elif self.radio_sh_cheb_2.isChecked():
            self.type = 'sh_cheb_2'
        self.custom_func_struct = self.custom_check.isChecked()
        self.input_path = self.line_input.text()
        self.output_path = self.line_output.text()
        self.samples_num = self.sample_spin.value()
        self.lambda_multiblock = self.lambda_check.isChecked()
        self.weight_method = self.weights_box.currentText().lower()
        self.manager = None


        #set tablewidget
        self.tablewidget.verticalHeader().hide()
        self.tablewidget.setRowCount(0)
        column_size = [60, 70, 100, 100,200, 60, 200,80]
        for index, size in enumerate(column_size):
             self.tablewidget.setColumnWidth(index,size)
        return

    @pyqtSlot()
    def input_clicked(self):
        filename = QFileDialog.getOpenFileName(self, 'Open data file', '.', 'Data file (*.xlsx)')[0]
        if filename == '':
            return
        if filename != self.input_path:
            self.input_path = filename
            self.input_changed.emit(filename)
        return

    @pyqtSlot('QString')
    def input_modified(self, value):
        if value != self.input_path:
            self.input_path = value
        return

    @pyqtSlot()
    def output_clicked(self):
        filename = QFileDialog.getSaveFileName(self, 'Save data file', '.', 'Spreadsheet (*.xlsx)')[0]
        if filename == '':
            return
        if filename != self.output_path:
            self.output_path = filename
            self.output_changed.emit(filename)
        return

    @pyqtSlot('QString')
    def output_modified(self, value):
        if value != self.output_path:
            self.output_path = value
        return

    @pyqtSlot(int)
    def samples_modified(self, value):
        self.samples_num = value
        return

    @pyqtSlot(int)
    def dimension_modified(self, value):
        sender = self.sender().objectName()
        if sender == 'x1_dim':
            self.dimensions[0] = value
        elif sender == 'x2_dim':
            self.dimensions[1] = value
        elif sender == 'x3_dim':
            self.dimensions[2] = value
        elif sender == 'y_dim':
            self.dimensions[3] = value
        return

    @pyqtSlot(int)
    def degree_modified(self, value):
        sender = self.sender().objectName()
        if sender == 'x1_deg':
            self.degrees[0] = value
        elif sender == 'x2_deg':
            self.degrees[1] = value
        elif sender == 'x3_deg':
            self.degrees[2] = value
        return

    @pyqtSlot(bool)
    def type_modified(self, isdown):
        if (isdown):
            sender = self.sender().objectName()
            if sender == 'radio_sh_cheb':
                self.type = 'sh_cheb_doubled'
            elif sender == 'radio_cheb':
                self.type = 'cheb'
            elif sender == 'radio_sh_cheb_2':
                self.type = 'sh_cheb_2'
        return

    @pyqtSlot(bool)
    def structure_changed(self, isdown):
        self.custom_func_struct = isdown

    @pyqtSlot()
    def plot_clicked(self):
        if self.manager:
            try:
                self.manager.plot(self.predictBox.value())
            except Exception as e:
                QMessageBox.warning(self,'Error!','Error happened during plotting: ' + str(e))
        return

    @pyqtSlot()
    def exec_clicked(self):
        self.exec_button.setEnabled(False)
        try:
            self.tablewidget.setRowCount(self.predictBox.value())
            self.manager = SolverManager(self._get_params())
            self.manager.prepare(self.input_path)
        except Exception as e:
            QMessageBox.warning(self,'Error!','Error happened during execution: ' + str(e))
        self.exec_button.setEnabled(True)
        return

    @pyqtSlot()
    def bruteforce_called(self):
        BruteForceWindow.launch(self)
        return

    @pyqtSlot(int, int, int)
    def update_degrees(self, x1_deg, x2_deg, x3_deg):
        self.x1_deg.setValue(x1_deg)
        self.x2_deg.setValue(x2_deg)
        self.x3_deg.setValue(x3_deg)
        return

    @pyqtSlot(bool)
    def lambda_calc_method_changed(self, isdown):
        self.lambda_multiblock = isdown
        return

    @pyqtSlot('QString')
    def weights_modified(self, value):
        self.weight_method = value.lower()
        return

    def _get_params(self):
        return dict(custom_struct=self.custom_func_struct,poly_type=self.type, degrees=self.degrees,
                    dimensions=self.dimensions,
                    samples=self.samples_num, output_file=self.output_path,
                    weights=self.weight_method, lambda_multiblock=self.lambda_multiblock,
                    pred_steps = self.predictBox.value(), tablewidget = self.tablewidget, \
                    lbl = {'rmr':self.lbl_rmr, 'time': self.lbl_time, 'y1': self.lbl_y1,\
                           'y2':self.lbl_y2, 'y3':self.lbl_y3})
Пример #38
0
class Question(QObject):

    """A question asked to the user, e.g. via the status bar.

    Note the creator is responsible for cleaning up the question after it
    doesn't need it anymore, e.g. via connecting Question.completed to
    Question.deleteLater.

    Attributes:
        mode: A PromptMode enum member.
              yesno: A question which can be answered with yes/no.
              text: A question which requires a free text answer.
              user_pwd: A question for a username and password.
        default: The default value.
                 For yesno, None (no default), True or False.
                 For text, a default text as string.
                 For user_pwd, a default username as string.
        title: The question title to show.
        text: The prompt text to display to the user.
        answer: The value the user entered (as password for user_pwd).
        is_aborted: Whether the question was aborted.
        interrupted: Whether the question was interrupted by another one.

    Signals:
        answered: Emitted when the question has been answered by the user.
                  arg: The answer to the question.
        cancelled: Emitted when the question has been cancelled by the user.
        aborted: Emitted when the question was aborted programmatically.
                 In this case, cancelled is not emitted.
        answered_yes: Convenience signal emitted when a yesno question was
                      answered with yes.
        answered_no: Convenience signal emitted when a yesno question was
                     answered with no.
        completed: Emitted when the question was completed in any way.
    """

    answered = pyqtSignal(object)
    cancelled = pyqtSignal()
    aborted = pyqtSignal()
    answered_yes = pyqtSignal()
    answered_no = pyqtSignal()
    completed = pyqtSignal()

    def __init__(self, parent=None):
        super().__init__(parent)
        self._mode = None
        self.default = None
        self.title = None
        self.text = None
        self.answer = None
        self.is_aborted = False
        self.interrupted = False

    def __repr__(self):
        return utils.get_repr(self, title=self.title, text=self.text,
                              mode=self._mode, default=self.default)

    @property
    def mode(self):
        """Getter for mode so we can define a setter."""
        return self._mode

    @mode.setter
    def mode(self, val):
        """Setter for mode to do basic type checking."""
        if not isinstance(val, PromptMode):
            raise TypeError("Mode {} is no PromptMode member!".format(val))
        self._mode = val

    @pyqtSlot()
    def done(self):
        """Must be called when the question was answered completely."""
        self.answered.emit(self.answer)
        if self.mode == PromptMode.yesno:
            if self.answer:
                self.answered_yes.emit()
            else:
                self.answered_no.emit()
        self.completed.emit()

    @pyqtSlot()
    def cancel(self):
        """Cancel the question (resulting from user-input)."""
        self.cancelled.emit()
        self.completed.emit()

    @pyqtSlot()
    def abort(self):
        """Abort the question."""
        if self.is_aborted:
            log.misc.debug("Question was already aborted")
            return
        self.is_aborted = True
        try:
            self.aborted.emit()
            self.completed.emit()
        except TypeError:
            # WORKAROUND
            # We seem to get "pyqtSignal must be bound to a QObject, not
            # 'Question' here, which makes no sense at all..."
            log.misc.exception("Error while aborting question")
Пример #39
0
    class server(QWidget, base_server, cluster):

        sim_finished = pyqtSignal()

        def __init__(self):
            QWidget.__init__(self)
            base_server.__init__(self)
            self.enable_gui = False
            self.callback_when_done = False
            self.display = False
            self.fit_update = None
            self.excel_workbook_gen_error = False
            status_icon_init()
            self.gui_update_time = time.time()
            self.timer = QTimer()

        def init(self, sim_dir):
            self.base_server_init(sim_dir)

            self.cluster_init()
            #self.cluster=str2bool(inp_get_token_value("server.inp","#cluster"))

        def set_terminal(self, terminal):
            self.terminal = terminal

        def set_display_function(self, display):
            self.display = display

        def set_fit_update_function(self, fit_update):
            self.fit_update = fit_update

        def gui_sim_start(self):
            help_window().hide()
            self.progress_window.start()
            status_icon_run(self.cluster)
            self.extern_gui_sim_start()

        def set_callback_when_done(self, proc):
            self.callback_when_done = proc

        def gui_sim_stop(self):
            text = self.check_warnings()
            self.progress_window.stop()
            help_window().show()
            status_icon_stop(self.cluster)

            help_window().help_set_help([
                "plot.png",
                _("<big><b>Simulation finished!</b></big><br>Click on the plot icon to plot the results"
                  )
            ])

            if len(text) != 0:
                self.dialog = sim_warnings(text)

            if self.callback_when_done != False:
                self.callback_when_done()
                self.callback_when_done = False

            if self.excel_workbook_gen_error == True:
                help_window().help_append([
                    "warning.png",
                    _("<big><b>Excel workbook error</b></big><br>I can't write new data to the file data.xlsx, I think you have are viewing it using another program.  Please close data.xlsx to enable me to write new data to it."
                      )
                ])

            self.jobs_update.emit()
            self.sim_finished.emit()

        def setup_gui(self, extern_gui_sim_start):
            self.enable_gui = True
            self.extern_gui_sim_start = extern_gui_sim_start

        def add_job(self, path, arg):
            if self.cluster == False:
                self.base_server_add_job(path, arg)
                self.jobs_update.emit()
            else:
                self.add_remote_job(path)
                self.send_dir(path, "")

        def start(self):
            self.excel_workbook_gen_error = False
            self.finished_jobs = []
            if self.interactive_cluster == True or self.cluster == False:
                self.progress_window.show()

                if self.enable_gui == True:
                    self.gui_sim_start()

            self.running = True
            self.run_jobs()

        def run_jobs(self):
            self.stop_work = False
            if self.cluster == True:
                self.cluster_run_jobs()
            else:
                self.timer_start()

        def process_jobs(self):

            path = True
            while (path != False):
                path, command = self.base_server_get_next_job_to_run()
                self.jobs_update.emit()
                if path != False:
                    if self.terminal.run(path, command) == True:
                        time.sleep(0.1)
                        return True
                    else:
                        return False
                else:
                    return

        def stop(self):
            self.timer.stop()

            self.progress_window.set_fraction(0.0)
            if self.interactive_cluster == True or self.cluster == False:
                self.gui_sim_stop()

            self.jobs = []
            self.jobs_running = 0
            self.jobs_run = 0
            self.running = False

            if self.display != False:

                self.display()

            #print(_("I have shut down the server."))

        def my_timer(self):
            #This is to give the QProcess timer enough time to update
            if self.terminal.test_free_cpus() != 0:
                self.process_jobs()

        def timer_start(self):
            self.timer.timeout.connect(self.my_timer)
            self.timer.start(100)

        def callback_dbus(self, data_in):
            if data_in.startswith("hex"):
                data_in = data_in[3:]
                data = codecs.decode(data_in, 'hex')
                data = data.decode('ascii')

                if data.startswith("lock"):
                    if len(self.jobs) == 0:
                        print(_("I did not think I was running any jobs"))
                        self.stop()
                    else:
                        if self.finished_jobs.count(data) == 0:
                            job = int(data[4:])
                            self.base_job_finished(job)

                            self.finished_jobs.append(data)
                            make_work_book = inp_get_token_value(
                                os.path.join(get_sim_path(), "dump.inp"),
                                "#dump_workbook")
                            if make_work_book != None:
                                if str2bool(make_work_book) == True:
                                    if gen_workbook(
                                            self.jobs[job].path,
                                            os.path.join(
                                                self.jobs[job].path,
                                                "data.xlsx")) == False:
                                        self.excel_workbook_gen_error = self.excel_workbook_gen_error or True

                            self.progress_window.set_fraction(
                                float(self.jobs_run) / float(len(self.jobs)))

                            if (self.jobs_run == len(self.jobs)):
                                self.stop()

                elif (data == "pulse"):
                    if len(self.jobs) == 1:
                        splitup = data.split(":")
                        if len(splitup) > 1:
                            text = data.split(":")[1]
                            self.progress_window.set_text(text)
                        #self.progress_window.progress.set_pulse_step(0.01)
                        self.progress_window.pulse()
                elif (data.startswith("enable_pulse")):
                    splitup = data.split(":")
                    if len(splitup) > 1:
                        value = str2bool(data.split(":")[1])
                        self.progress_window.enable_pulse(value)
                elif (data.startswith("percent")):
                    if len(self.jobs) == 1:
                        splitup = data.split(":")
                        if len(splitup) > 1:
                            frac = float(data.split(":")[1])
                            self.progress_window.set_fraction(frac)
                elif (data.startswith("text")):
                    if len(self.jobs) == 1:
                        splitup = data.split(":")
                        if len(splitup) > 1:
                            self.progress_window.set_text(data.split(":")[1])
                elif (data.startswith("fit_run")):
                    elapsed_time = time.time() - self.gui_update_time
                    if elapsed_time > 5:

                        self.gui_update_time = time.time()

                        if self.fit_update != None:
                            self.fit_update()
Пример #40
0
class Theme(QObject):
    def __init__(self, engine, parent=None) -> None:
        super().__init__(parent)

        self._engine = engine
        self._path = ""
        self._icons = {}  # type: Dict[str, Dict[str, QUrl]]
        self._deprecated_icons = {}  # type: Dict[str, Dict[str, str]]
        self._images = {}  # type: Dict[str, QUrl]

        # Workaround for incorrect default font on Windows
        if sys.platform == "win32":
            default_font = QFont()
            default_font.setPointSize(9)
            QCoreApplication.instance().setFont(default_font)

        self._em_height = int(
            QFontMetrics(QCoreApplication.instance().font()).ascent())
        self._em_width = self._em_height

        # Cache the initial language in the preferences. For fonts, a special font can be defined with, for example,
        # "medium" and "medium_nl_NL". If the special one exists, getFont() will return that, otherwise the default
        # will be returned. We cache the initial language here is because Cura can only change its language if it gets
        # restarted, so we need to keep the fonts consistent in a single Cura run.
        self._preferences = UM.Application.Application.getInstance(
        ).getPreferences()
        self._lang_code = self._preferences.getValue("general/language")

        self._initializeDefaults()

        self.reload()

    themeLoaded = pyqtSignal()

    def reload(self):
        self._path = ""
        self._icons = {}
        self._images = {}
        application = UM.Application.Application.getInstance()
        application.getPreferences().addPreference("general/theme",
                                                   application.default_theme)
        try:
            theme_path = Resources.getPath(
                Resources.Themes,
                application.getPreferences().getValue("general/theme"))
            self.load(theme_path)
        except FileNotFoundError:
            Logger.log(
                "e",
                "Could not find theme file, resetting to the default theme.")

            # cannot the current theme, so go back to the default
            application.getPreferences().setValue("general/theme",
                                                  application.default_theme)
            theme_path = Resources.getPath(
                Resources.Themes,
                application.getPreferences().getValue("general/theme"))
            self.load(theme_path)

    @pyqtSlot(result="QVariantList")
    def getThemes(self) -> List[Dict[str, str]]:
        themes = []
        for path in Resources.getAllPathsForType(Resources.Themes):
            try:
                for file in os.listdir(path):
                    folder = os.path.join(path, file)
                    theme_file = os.path.join(folder, "theme.json")
                    if os.path.isdir(folder) and os.path.isfile(theme_file):
                        theme_id = os.path.basename(folder)

                        with open(theme_file, encoding="utf-8") as f:
                            try:
                                data = json.load(f)
                            except (UnicodeDecodeError,
                                    json.decoder.JSONDecodeError):
                                Logger.log("w", "Could not parse theme %s",
                                           theme_id)
                                continue  # do not add this theme to the list, but continue looking for other themes

                            try:
                                theme_name = data["metadata"]["name"]
                            except KeyError:
                                Logger.log(
                                    "w",
                                    "Theme %s does not have a name; using its id instead",
                                    theme_id)
                                theme_name = theme_id  # fallback if no name is specified in json

                        themes.append({"id": theme_id, "name": theme_name})
            except EnvironmentError:
                pass
        themes.sort(key=lambda k: k["name"])

        return themes

    @pyqtSlot(str, result="QColor")
    def getColor(self, color: str) -> QColor:
        if color in self._colors:
            return self._colors[color]

        Logger.log("w", "No color %s defined in Theme", color)
        return QColor()

    @pyqtSlot(str, result="QSizeF")
    def getSize(self, size) -> QSizeF:
        if size in self._sizes:
            return self._sizes[size]

        Logger.log("w", "No size %s defined in Theme", size)
        return QSizeF()

    @pyqtSlot(str, str, result="QUrl")
    @pyqtSlot(str, result="QUrl")
    def getIcon(self, icon_name: str, detail_level: str = "default") -> QUrl:
        """
        Finds and returns the url of the requested icon. The icons are organized in folders according to their detail
        level and the same icon may exist with more details. If a detail level is not specified, the icon will be
        retrieved from the "default" folder. Icons with a higher detail level are recommended to be used with a bigger
        width/height.

        :param icon_name: The name of the icon to be retrieved. The same icon may exist in multiple detail levels.
        :param detail_level: The level of detail of the icon. Choice between "low, "default", "medium", "high".
        :return: The file url of the requested icon, in the requested detail level.
        """
        if detail_level in self._icons:
            if icon_name in self._icons[detail_level]:
                return self._icons[detail_level][icon_name]
        elif icon_name in self._icons[
                "icons"]:  # Retrieve the "old" icon from the base icon folder
            return self._icons["icons"][icon_name]

        if icon_name in self._deprecated_icons:
            new_icon = self._deprecated_icons[icon_name]["new_icon"]
            warning = f"The icon '{icon_name}' is deprecated. Please use icon '{new_icon}' instead."

            Logger.log("w_once", warning)
            warnings.warn(warning, DeprecationWarning, stacklevel=2)
            return self.getIcon(self._deprecated_icons[icon_name]["new_icon"],
                                self._deprecated_icons[icon_name]["size"])

        # We don't log this anymore since we have new fallback behavior to load the icon from a plugin folder
        # Logger.log("w", "No icon %s defined in Theme", icon_name)
        return QUrl()

    @pyqtSlot(str, result="QUrl")
    def getImage(self, image_name: str) -> QUrl:
        if image_name in self._images:
            return self._images[image_name]

        Logger.log("w", "No image %s defined in Theme", image_name)
        return QUrl()

    @pyqtSlot(str, result="QFont")
    def getFont(self, font_name: str) -> QFont:
        lang_specific_font_name = "%s_%s" % (font_name, self._lang_code)
        if lang_specific_font_name in self._fonts:
            return self._fonts[lang_specific_font_name]

        if font_name in self._fonts:
            return self._fonts[font_name]

        Logger.log("w", "No font %s defined in Theme", font_name)
        return QFont()

    @pyqtSlot(str)
    def load(self, path: str, is_first_call: bool = True) -> None:
        if path == self._path:
            return

        theme_full_path = os.path.join(path, "theme.json")
        Logger.log(
            "d", "Loading theme file: {theme_full_path}".format(
                theme_full_path=theme_full_path))
        try:
            with open(theme_full_path, encoding="utf-8") as f:
                data = json.load(f)
        except EnvironmentError as e:
            Logger.error(
                "Unable to load theme file at {theme_full_path}: {err}".format(
                    theme_full_path=theme_full_path, err=e))
            return
        except UnicodeDecodeError:
            Logger.error(
                "Theme file at {theme_full_path} is corrupt (invalid UTF-8 bytes)."
                .format(theme_full_path=theme_full_path))
            return
        except json.JSONDecodeError:
            Logger.error(
                "Theme file at {theme_full_path} is corrupt (invalid JSON syntax)."
                .format(theme_full_path=theme_full_path))
            return

        # Iteratively load inherited themes
        try:
            theme_id = data["metadata"]["inherits"]
            self.load(Resources.getPath(Resources.Themes, theme_id),
                      is_first_call=False)
        except FileNotFoundError:
            Logger.log("e", "Could not find inherited theme %s", theme_id)
        except KeyError:
            pass  # No metadata or no inherits keyword in the theme.json file

        if "colors" in data:
            for name, value in data["colors"].items():

                if not is_first_call and isinstance(value, str):
                    # Keep parent theme string colors as strings and parse later
                    self._colors[name] = value
                    continue

                if isinstance(value, str) and is_first_call:
                    # value is reference to base_colors color name
                    try:
                        color = data["base_colors"][value]
                    except IndexError:
                        Logger.log(
                            "w",
                            "Colour {value} could not be found in base_colors".
                            format(value=value))
                        continue
                else:
                    color = value

                try:
                    c = QColor(color[0], color[1], color[2], color[3])
                except IndexError:  # Color doesn't have enough components.
                    Logger.log(
                        "w",
                        "Colour {name} doesn't have enough components. Need to have 4, but had {num_components}."
                        .format(name=name, num_components=len(color)))
                    continue  # Skip this one then.
                self._colors[name] = c

        if "base_colors" in data:
            for name, color in data["base_colors"].items():
                try:
                    c = QColor(color[0], color[1], color[2], color[3])
                except IndexError:  # Color doesn't have enough components.
                    Logger.log(
                        "w",
                        "Colour {name} doesn't have enough components. Need to have 4, but had {num_components}."
                        .format(name=name, num_components=len(color)))
                    continue  # Skip this one then.
                self._colors[name] = c

        if is_first_call and self._colors:
            #Convert all string value colors to their referenced color
            for name, color in self._colors.items():
                if isinstance(color, str):
                    try:
                        c = self._colors[color]
                        self._colors[name] = c
                    except:
                        Logger.log(
                            "w",
                            "Colour {name} {color} does".format(name=name,
                                                                color=color))

        fonts_dir = os.path.join(path, "fonts")
        if os.path.isdir(fonts_dir):
            for root, dirnames, filenames in os.walk(fonts_dir):
                for filename in filenames:
                    if filename.lower().endswith(".ttf"):
                        QFontDatabase.addApplicationFont(
                            os.path.join(root, filename))

        if "fonts" in data:
            system_font_size = QCoreApplication.instance().font().pointSize()
            for name, font in data["fonts"].items():
                q_font = QFont()
                q_font.setFamily(
                    font.get("family",
                             QCoreApplication.instance().font().family()))

                if font.get("bold"):
                    q_font.setBold(font.get("bold", False))
                else:
                    q_font.setWeight(font.get("weight", 50))

                q_font.setLetterSpacing(QFont.AbsoluteSpacing,
                                        font.get("letterSpacing", 0))
                q_font.setItalic(font.get("italic", False))
                q_font.setPointSize(int(
                    font.get("size", 1) * system_font_size))
                q_font.setCapitalization(QFont.AllUppercase if font.get(
                    "capitalize", False) else QFont.MixedCase)

                self._fonts[name] = q_font

        if "sizes" in data:
            for name, size in data["sizes"].items():
                s = QSizeF()
                s.setWidth(round(size[0] * self._em_width))
                s.setHeight(round(size[1] * self._em_height))

                self._sizes[name] = s

        iconsdir = os.path.join(path, "icons")
        if os.path.isdir(iconsdir):
            try:
                for base_path, _, icons in os.walk(iconsdir):
                    detail_level = base_path.split(os.sep)[-1]
                    if detail_level not in self._icons:
                        self._icons[detail_level] = {}
                    for icon in icons:
                        name = os.path.splitext(icon)[0]
                        self._icons[detail_level][name] = QUrl.fromLocalFile(
                            os.path.join(base_path, icon))
            except EnvironmentError:  # Exception when calling os.walk, e.g. no access rights.
                pass  # Won't get any icons then. Images will show as black squares.

            deprecated_icons_file = os.path.join(iconsdir,
                                                 "deprecated_icons.json")
            if os.path.isfile(deprecated_icons_file):
                try:
                    with open(deprecated_icons_file, encoding="utf-8") as f:
                        data = json.load(f)
                        for icon in data:
                            self._deprecated_icons[icon] = data[icon]
                except (UnicodeDecodeError, json.decoder.JSONDecodeError,
                        EnvironmentError):
                    Logger.logException(
                        "w", "Could not parse deprecated icons list %s",
                        deprecated_icons_file)

        imagesdir = os.path.join(path, "images")
        if os.path.isdir(imagesdir):
            for image in os.listdir(imagesdir):
                name = os.path.splitext(image)[0]
                self._images[name] = QUrl.fromLocalFile(
                    os.path.join(imagesdir, image))

        Logger.log("d", "Loaded theme %s", path)
        Logger.info(f"System's em size is {self._em_height}px.")
        self._path = path

        # only emit the theme loaded signal once after all the themes in the inheritance chain have been loaded
        if is_first_call:
            self.themeLoaded.emit()

    def _initializeDefaults(self) -> None:
        self._fonts = {
            "system": QCoreApplication.instance().font(),
            "fixed": QFontDatabase.systemFont(QFontDatabase.FixedFont)
        }

        palette = QCoreApplication.instance().palette()
        self._colors = {
            "system_window": palette.window(),
            "system_text": palette.text()
        }

        self._sizes = {"line": QSizeF(self._em_width, self._em_height)}

    @classmethod
    def getInstance(cls, engine=None) -> "Theme":
        """Get the singleton instance for this class."""

        # Note: Explicit use of class name to prevent issues with inheritance.
        if Theme.__instance is None:
            Theme.__instance = cls(engine)
        return Theme.__instance

    __instance = None  # type: "Theme"
Пример #41
0
class Console(QWidget):
    errorSignal = pyqtSignal(str)
    outputSignal = pyqtSignal(str)

    def __init__(self, parent=None):
        super().__init__()
        self.parent = parent
        self.pressed = False
        self.font = QFont()
        self.dialog = MessageBox(self)
        # self.font.setFamily(editor["editorFont"])
        self.font.setFamily("Iosevka")
        self.font.setPointSize(12)
        self.layout = QVBoxLayout()

        self.setLayout(self.layout)
        self.output = None
        self.setFocusPolicy(Qt.StrongFocus)
        self.error = None
        self.finished = False
        self.clicked = False

        self.process = QProcess()
        self.state = None
        self.process.readyReadStandardError.connect(self.onReadyReadStandardError)
        self.process.readyReadStandardOutput.connect(self.onReadyReadStandardOutput)
        self.add()  # Add items to the layout

    def ispressed(self):
        return self.pressed

    def added(self):
        self.pressed = True

    def remove(self):
        self.parent.hideFileExecuter()
        self.clicked = True

    def hideTerminalClicked(self):
        return self.clicked

    def onReadyReadStandardError(self):
        try:
            self.error = self.process.readAllStandardError().data().decode()

            self.editor.appendPlainText(self.error)

            self.errorSignal.emit(self.error)
            if self.error == "":
                pass
            else:
                self.error = self.error.split(os.linesep)[-2]
                self.dialog.helpword = str(self.error)
                self.dialog.getHelp(self.parent.parent)
        except IndexError as E:
            print(E, " on line 70 in the file Console.py")

    def onReadyReadStandardOutput(self):
        try:
            self.result = self.process.readAllStandardOutput().data().decode()
        except UnicodeDecodeError as E:
            print(E, " on line 76 in the file Console.py")
        try:
            self.editor.appendPlainText(self.result.strip("\n"))
            self.state = self.process.state()
        except RuntimeError:
            pass

        self.outputSignal.emit(self.result)

    def ifFinished(self, exitCode, exitStatus):
        self.finished = True

    def add(self):
        """Executes a system command."""
        # clear previous text
        self.added()
        self.button = QPushButton("Hide terminal")
        self.button.setFont(QFont("Iosevka", 11))
        self.button.setStyleSheet("""
                height: 20;
                background-color: #212121;

                """)
        self.terminateButton = QPushButton("   Stop")
        self.terminateButton.setIcon(QIcon("resources/square.png"))
        self.terminateButton.setFont(QFont("Iosevka", 11))
        self.terminateButton.clicked.connect(self.terminate)
        self.button.setFixedWidth(120)
        self.h_layout = QHBoxLayout()
        self.editor = Editor(self)
        self.editor.setReadOnly(True)
        self.editor.setFont(self.font)
        self.layout.addWidget(self.button)
        self.layout.addWidget(self.editor)
        self.layout.addWidget(self.terminateButton)
        self.button.clicked.connect(self.remove)

    def run(self, command, path):  # Takes in the command and the path of the file
        os.chdir(os.path.dirname(path))  # We need to change the path to the path where the file is being ran from
        self.editor.clear()
        if self.process.state() == 1 or self.process.state() == 2:
            self.process.kill()
            self.editor.setPlainText("Process already started, terminating")
        else:
            self.process.start(command)

    def terminate(self):
        if self.process.state() == 2:
            self.process.kill()
Пример #42
0
class TOS(QTextEdit):
    tos_signal = pyqtSignal()
    error_signal = pyqtSignal(object)
Пример #43
0
class SerialManager(QObject):
    """
    A class to manage the serial port.

    It will try to send what it receive and send via the send_print signal. When
    it receive a 'ok' from the serial it will send the send_confirm signal.
    """
    send_print = pyqtSignal(str, str)
    send_confirm = pyqtSignal(bool)

    def __init__(self, serial, fake_mode=False):
        """
        The __init__ method.

        :param serial: The serial port.
        :param fake_mode: if True, then the manager will not use the serial
            object, and will always respond 'ok'.
        """
        super(SerialManager, self).__init__()
        self.serial = serial
        self.fake_mode = fake_mode
        self.is_open = self.serial.isOpen()
        self.something_sent = False

    def open(self, baudrate, serial_port, timeout):
        """
        Opens the serial port with the given parameters.

        :param baudrate: The baudrate.
        :param serial_port: The port to be used.
        :param timeout: Timeout for reading and writing.
        """
        logger.info("Opening {} with baudrate {}, timeout {}".format(
            repr(serial_port), baudrate, timeout))
        self.send_print.emit("Opening {} with baudrate {}, timeout {}".format(
            repr(serial_port), baudrate, timeout), "info")
        self.serial.port = serial_port
        self.serial.baudrate = baudrate
        self.serial.timeout = timeout
        self.serial.write_timeout = timeout
        try:
            self.serial.open()
            self.is_open = True
        except serial.serialutil.SerialException:
            self.is_open = False
            logger.error("Could not open serial port.")
            self.send_print.emit("Could not open serial port.", "error")
            return False
        logger.debug(self.serial.timeout)
        return True

    def close(self):
        """
        Closes the serial port.
        """
        logger.info("Closing serial port.")
        self.send_print.emit("Closing serial port.", "info")
        self.is_open = False
        self.serial.close()

    def sendMsg(self, msg):
        """
        Sends a message using the serial port if fake_mode is False.

        :param msg: The message to be sent.
        :returns: True if no error occurred, else False.
        """
        if not self.fake_mode and not (self.serial and self.serial.isOpen()):
            self.send_print.emit("Error, no serial port to write.", "error")
            logger.error("Serial manager has no serial opened to send data.")
            return False
        elif self.fake_mode:
            self.send_print.emit(msg, "operator")
            logger.info(
                "Sending {} through fake serial port".format(repr(msg)))
            self.something_sent = True
            return True
        else:
            logger.info("Sending {} through {}".format(repr(msg), self.serial))
            self.send_print.emit(msg, "operator")
            if msg[-1] != '\n':
                msg += '\n'
            try:
                self.serial.write(bytes(msg, encoding='utf8'))
            except serial.serialutil.SerialException as e:
                logger.error(e)
            return True

    @pyqtSlot()
    def readMsg(self):
        """
        Reads a line from the serial port. And emit the send_print or
        send_confirm signals if necessary.
        """
        if self.fake_mode:
            if self.something_sent:
                logger.info("Received {}".format(repr("ok")))
                self.send_print.emit("ok", "machine")
                self.something_sent = False
                self.send_confirm.emit(True)
            return
        elif not (self.serial and self.serial.isOpen()):
            self.send_print.emit("Error, no serial port to read.", "error")
            logger.error("Serial manager has no serial opened to read data.")
            self.send_confirm.emit(False)
            return
        elif not self.serial.in_waiting:
            return
        txt = ""
        try:
            txt = self.serial.readline().decode('ascii')
        except serial.serialutil.SerialException as e:
            logger.error("Serial error : {}".format(e))
            self.send_print.emit("Serial error while reading.", "error")
        except UnicodeDecodeError as e:
            logger.error("Serial error : {}".format(e))
            self.send_print.emit("Serial error while reading.", "error")

        if txt:
            if "error" in txt.lower():
                self.send_print.emit(txt, "error")
            else:
                self.send_print.emit("m> {}".format(txt), "machine")
            self.send_confirm.emit("ok" in txt.lower())
Пример #44
0
class OctoPrintExtension(QObject, Extension, OutputDevicePlugin):
    def __init__(self, parent=None):
        QObject.__init__(self, parent)
        Extension.__init__(self)
        OutputDevicePlugin.__init__(self)
        self.addMenuItem(catalog.i18n("OctoPrint Servers"),
                         self.showSettingsDialog)
        self._dialogs = {}
        self._dialogView = None

        Preferences.getInstance().addPreference("octoprint/instances",
                                                json.dumps({}))
        self._instances = json.loads(
            Preferences.getInstance().getValue("octoprint/instances"))

    def start(self):
        manager = self.getOutputDeviceManager()
        for name, instance in self._instances.items():
            manager.addOutputDevice(
                OctoPrintOutputDevice.OctoPrintOutputDevice(
                    name, instance["url"], instance["apikey"]))

    def stop(self):
        manager = self.getOutputDeviceManager()
        for name in self._instances.keys():
            manager.removeOutputDevice(name)

    def _createDialog(self, qml):
        path = QUrl.fromLocalFile(
            os.path.join(os.path.dirname(os.path.abspath(__file__)), qml))
        self._component = QQmlComponent(Application.getInstance()._engine,
                                        path)
        self._context = QQmlContext(
            Application.getInstance()._engine.rootContext())
        self._context.setContextProperty("manager", self)
        dialog = self._component.create(self._context)
        if dialog is None:
            Logger.log("e", "QQmlComponent status %s",
                       self._component.status())
            Logger.log("e", "QQmlComponent errorString %s",
                       self._component.errorString())
            raise RuntimeError(self._component.errorString())
        return dialog

    def _showDialog(self, qml):
        if not qml in self._dialogs:
            self._dialogs[qml] = self._createDialog(qml)
        self._dialogs[qml].show()

    def showSettingsDialog(self):
        self._showDialog("OctoPrintPlugin.qml")

    serverListChanged = pyqtSignal()

    @pyqtProperty("QVariantList", notify=serverListChanged)
    def serverList(self):
        return list(self._instances.keys())

    @pyqtSlot(str, result=str)
    def instanceUrl(self, name):
        if name in self._instances.keys():
            return self._instances[name]["url"]
        return None

    @pyqtSlot(str, result=str)
    def instanceApiKey(self, name):
        if name in self._instances.keys():
            return self._instances[name]["apikey"]
        return None

    @pyqtSlot(str, str, str, str)
    def saveInstance(self, oldName, name, url, apiKey):
        manager = self.getOutputDeviceManager()
        if oldName and oldName != name:
            manager.removeOutputDevice(oldName)
            del self._instances[oldName]
        self._instances[name] = {"url": url, "apikey": apiKey}
        manager.addOutputDevice(
            OctoPrintOutputDevice.OctoPrintOutputDevice(name, url, apiKey))
        Preferences.getInstance().setValue("octoprint/instances",
                                           json.dumps(self._instances))
        self.serverListChanged.emit()

    @pyqtSlot(str)
    def removeInstance(self, name):
        self.getOutputDeviceManager().removeOutputDevice(name)
        del self._instances[name]
        Preferences.getInstance().setValue("octoprint/instances",
                                           json.dumps(self._instances))
        self.serverListChanged.emit()

    @pyqtSlot(str, str, result=bool)
    def validName(self, oldName, newName):
        # empty string isn't allowed
        if not newName:
            return False
        # if name hasn't changed, not a duplicate, just no rename
        if oldName == newName:
            return True

        # duplicates not allowed
        return (not newName in self._instances.keys())
Пример #45
0
class DBSyncMenu(QMainWindow):
    msgSignal = pyqtSignal(str)  # timely show

    def __init__(self):
        super(DBSyncMenu, self).__init__()
        self.dbSync = DBSync()
        self.initUI()

    def initUI(self):
        menubar = self.menuBar()

        self.textEd = QTextEdit()
        self.textEd.setReadOnly(True)
        font = QFont()
        font.setPointSize(14)
        self.textEd.setFont(font)
        self.setCentralWidget(self.textEd)

        self.config_menu_init(menubar)
        self.run_menu_init(menubar)

        self.setGeometry(700, 300, 400, 300)
        self.setWindowTitle('数据库同步工具')
        self.show()

    def config_menu_init(self, baseMenu):
        configMenu = baseMenu.addMenu("配置")

        dbSetAct = QAction("配置数据库信息", self)
        dbSetAct.triggered.connect(self.set_db_info)
        configMenu.addAction(dbSetAct)

        dbShowAct = QAction("显示数据库信息", self)
        dbShowAct.triggered.connect(self.show_db_info)
        configMenu.addAction(dbShowAct)

    def run_menu_init(self, baseMenu):
        runMenu = baseMenu.addMenu("运行")

        #clear data file
        clearAct = QAction("清理数据文件", self)
        clearAct.triggered.connect(self.clear_data_file)
        runMenu.addAction(clearAct)

        runMenu.addSeparator()

        #table menu
        tableMenu = runMenu.addMenu("数据表")
        self.table_menu_init(tableMenu)

        #field menu
        fieldMenu = runMenu.addMenu("表字段")
        self.field_menu_init(fieldMenu)

        #constraints menu
        constrMenu = runMenu.addMenu("表约束")
        self.constraints_menu_init(constrMenu)

        runMenu.addSeparator()

        #sync
        syncAct = QAction("同步数据库", self)
        syncAct.triggered.connect(self.sync_database)
        runMenu.addAction(syncAct)

    def table_menu_init(self, baseMenu):
        createTableAct = QAction("创建表", self)
        createTableAct.triggered.connect(self.create_table)
        baseMenu.addAction(createTableAct)

        dropTableAct = QAction("销毁表", self)
        dropTableAct.triggered.connect(self.drop_table)
        baseMenu.addAction(dropTableAct)

    def field_menu_init(self, baseMenu):
        # add table field
        addFieldAct = QAction("添加字段", self)
        addFieldAct.triggered.connect(self.add_field)
        baseMenu.addAction(addFieldAct)

        # modify table field
        # modifyFieldMenu = baseMenu.addMenu("修改字段")
        # self.modify_field_menu_init(modifyFieldMenu)

        # delete table field
        delFieldAct = QAction("删除字段", self)
        delFieldAct.triggered.connect(self.delete_field)
        baseMenu.addAction(delFieldAct)

    def modify_field_menu_init(self, baseMenu):
        modifyNameAct = QAction("修改字段名称", self)
        modifyNameAct.triggered.connect(self.modify_field)
        baseMenu.addAction(modifyNameAct)

        modifyTypeAct = QAction("修改字段属性", self)
        modifyTypeAct.triggered.connect(self.modify_field)
        baseMenu.addAction(modifyTypeAct)

    def constraints_menu_init(self, baseMenu):
        addRowAct = QAction("添加行级约束", self)
        addRowAct.triggered.connect(self.add_row_constraints)
        baseMenu.addAction(addRowAct)

        addTableAct = QAction("添加表级约束", self)
        addTableAct.triggered.connect(self.add_table_constraints)
        baseMenu.addAction(addTableAct)

        baseMenu.addSeparator()

        delRowAct = QAction("删除行级约束", self)
        delRowAct.triggered.connect(self.del_row_constraints)
        baseMenu.addAction(delRowAct)

        delTableAct = QAction("删除表级约束", self)
        delTableAct.triggered.connect(self.del_table_constraints)
        baseMenu.addAction(delTableAct)

    def set_db_info(self):
        self.ui = SetDBWindow(self.dbSync)
        self.ui.show()

    def show_db_info(self):
        text = '''HOST: %s\nPORT: %s\nUSER: %s\nPASSWD: %s\n''' % (
            self.dbSync.host, str(
                self.dbSync.port), self.dbSync.user, self.dbSync.passwd)
        self.msgSignal.emit("clear")
        self.msgSignal.emit(text)

    def display_msg(self, msg):
        if msg == "clear":
            self.textEd.clear()
        else:
            if msg.find("[ERROR]") != -1: self.textEd.setTextColor(Qt.red)
            self.textEd.append(msg)
            self.textEd.setTextColor(Qt.black)

    def clear_data_file(self):
        reply = QMessageBox.question(self, "clear hint", "确定清空数据文件?",
                                     QMessageBox.Yes | QMessageBox.No)
        if reply == QMessageBox.Yes: self.dbSync.clear_data_file()

    def create_table(self):
        self.ui = CreateTableWindow(self.dbSync)
        self.ui.show()

    def drop_table(self):
        self.ui = DropTableWindow(self.dbSync)
        self.ui.show()

    def add_field(self):
        self.ui = AddFieldWindow(self.dbSync)
        self.ui.show()

    def modify_field(self):
        pass

    def delete_field(self):
        self.ui = DelFieldWindow(self.dbSync)
        self.ui.show()

    def add_row_constraints(self):
        self.ui = AddRowConstrWindow(self.dbSync)
        self.ui.show()

    def add_table_constraints(self):
        self.ui = AddTableConstrWindow(self.dbSync)
        self.ui.show()

    def del_row_constraints(self):
        self.ui = DelRowConstrWindow(self.dbSync)
        self.ui.show()

    def del_table_constraints(self):
        self.ui = DelTableConstrWindow(self.dbSync)
        self.ui.show()

    def sync_database(self):
        reply = QMessageBox.question(self, "sync hint",
                                     "同步前请确保配置了数据库信息和检查数据文件, 是否继续执行?",
                                     QMessageBox.Yes | QMessageBox.No)
        if reply == QMessageBox.Yes:
            try:
                self.dbSync.sync_database(self.msgSignal)
            except Exception as e:
                QMessageBox.warning(None, "warning", str(e))

    def keyPressEvent(self, e):
        if e.key() == Qt.Key_Escape:
            self.ui.close()
            self.close()
Пример #46
0
class ClosedTabsManager(QObject):
    """
    Class implementing a manager for closed tabs.
    
    @signal closedTabAvailable(boolean) emitted to signal a change of
        availability of closed tabs
    """
    closedTabAvailable = pyqtSignal(bool)
    
    def __init__(self, parent=None):
        """
        Constructor
        
        @param parent reference to the parent object (QObject)
        """
        super(ClosedTabsManager, self).__init__()
        
        self.__closedTabs = []
    
    def recordBrowser(self, browser, position):
        """
        Public method to record the data of a browser about to be closed.
        
        @param browser reference to the browser to be closed (HelpBrowser)
        @param position index of the tab to be closed (integer)
        """
        globalSettings = QWebSettings.globalSettings()
        if globalSettings.testAttribute(QWebSettings.PrivateBrowsingEnabled):
            return
        
        if browser.url().isEmpty():
            return
        
        tab = ClosedTab(browser.url(), browser.title(), position)
        self.__closedTabs.insert(0, tab)
        self.closedTabAvailable.emit(True)
    
    def getClosedTabAt(self, index):
        """
        Public method to get the indexed closed tab.
        
        @param index index of the tab to return (integer)
        @return requested tab (ClosedTab)
        """
        if len(self.__closedTabs) > 0 and len(self.__closedTabs) > index:
            tab = self.__closedTabs.pop(index)
        else:
            tab = ClosedTab()
        self.closedTabAvailable.emit(len(self.__closedTabs) > 0)
        return tab
    
    def isClosedTabAvailable(self):
        """
        Public method to check for closed tabs.
        
        @return flag indicating the availability of closed tab data (boolean)
        """
        return len(self.__closedTabs) > 0
    
    def clearList(self):
        """
        Public method to clear the list of closed tabs.
        """
        self.__closedTabs = []
        self.closedTabAvailable.emit(False)
    
    def allClosedTabs(self):
        """
        Public method to get a list of all closed tabs.
        
        @return list of closed tabs (list of ClosedTab)
        """
        return self.__closedTabs
Пример #47
0
class View(QWidget):

    sgnStop = pyqtSignal()

    def __init__(self):
        super().__init__()
        self._thread = None

        self.initUI()

    def initUI(self):

        central_widget = QWidget()
        layout = QGridLayout(central_widget)
        layout.setSpacing(10)

        self._thread = None
        if self._thread == None:
            self._thread = QThread()

        self._thread.start()
        self.vid = ShowVideo()
        self.vid.moveToThread(self._thread)
        image_viewer = ImageViewer()

        self.vid.VideoSignal.connect(image_viewer.setImage)
        self.sgnStop.connect(self.vid.stopVideo)

        # Button to start the videocapture:
        start_button = PicButton(QPixmap(join(images_path + "/play.png")),
                                 QPixmap(join(images_path + "/play.png")),
                                 QPixmap(join(images_path + "/play.png")))
        start_button.clicked.connect(self.vid.startVideo)
        start_button.setFixedWidth(50)
        start_button.setFixedHeight(50)

        # stop_button = QPushButton('Stop')
        stop_button = PicButton(QPixmap(join(images_path + "/rec.png")),
                                QPixmap(join(images_path + "/rec.png")),
                                QPixmap(join(images_path + "/rec.png")))

        stop_button.clicked.connect(self.disable)
        stop_button.setFixedWidth(45)
        stop_button.setFixedHeight(45)

        # void QGridLayout::addWidget(QWidget * widget, int fromRow, int fromColumn, int rowSpan, int columnSpan, Qt::Alignment alignment = 0)
        # layout.setContentsMargins(left, top, right, bottom)

        layout.setContentsMargins(100, 100, 100, 100)
        image_viewer.setFixedWidth(720)
        image_viewer.setFixedHeight(290)

        label = QLabel(self)
        pixmap = QPixmap(logo_path)
        pixmap = pixmap.scaled(pixmap.width() / 6, pixmap.height() / 6)
        label.setPixmap(pixmap)

        hbox_logo = QHBoxLayout()
        hbox_logo.addWidget(label)
        layout.addLayout(hbox_logo, 0, 0, 1, 1, Qt.AlignLeft)

        hbox_image = QHBoxLayout()
        hbox_image.addWidget(image_viewer)
        layout.addLayout(hbox_image, 0, 1, 1, 1, Qt.AlignLeft)

        frame_rate = QLabel("Select frame rate", self)
        comboBox = QComboBox(self)
        comboBox.addItem("6")
        comboBox.addItem("15")
        comboBox.addItem("30")
        comboBox.setCurrentIndex(1)
        comboBox.activated[str].connect(self.style_choice)

        hbox_dummy = QVBoxLayout()
        hbox_dummy.addWidget(frame_rate)
        hbox_dummy.addWidget(comboBox)
        layout.addLayout(hbox_dummy, 0, 2, 1, 1, Qt.AlignCenter)

        hbox_start_buttons = QHBoxLayout()
        hbox_start_buttons.addWidget(start_button)
        hbox_start_buttons.addWidget(stop_button)
        layout.addLayout(hbox_start_buttons, 0, 1, 1, 1,
                         Qt.AlignBottom | Qt.AlignCenter)

        layout.setVerticalSpacing(100)

        # 3d reconstruction
        reconstruct_btn = QPushButton()
        reconstruct_btn.clicked.connect(self.Reconstruct)

        reconstruct_btn.setIcon(QIcon(join(images_path + '/construct.png')))
        reconstruct_btn.setIconSize(QSize(70, 70))
        reconstruct_btn.setFixedWidth(100)
        reconstruct_btn.setFixedHeight(90)
        reconstruct_label = QLabel("  Reconstruct", self)

        # View pointclouds
        view_btn = QPushButton()
        view_btn.clicked.connect(self._viewReconstruction)
        # show_btn.resize(reconstruct_btn.sizeHint())
        view_btn.setIcon(QIcon(join(images_path + '/view.png')))
        view_btn.setIconSize(QSize(50, 50))
        view_btn.setFixedWidth(100)
        view_btn.setFixedHeight(90)
        view_label = QLabel("         View", self)

        # Segment
        segment_btn = QPushButton()
        segment_btn.clicked.connect(self.Augment)
        segment_btn.setIcon(QIcon(join(images_path + '/seg3.png')))
        segment_btn.setIconSize(QSize(80, 80))
        segment_btn.setFixedWidth(100)
        segment_btn.setFixedHeight(90)
        segmentation_label = QLabel("     Segment", self)

        # View pointclouds
        show_btn = QPushButton()
        show_btn.clicked.connect(self.Augment)
        show_btn.setIcon(QIcon(join(images_path + '/view1.png')))
        show_btn.setIconSize(QSize(50, 50))
        show_btn.setFixedWidth(100)
        show_btn.setFixedHeight(90)
        show_label = QLabel("         Show", self)

        # Augmentation
        augment_btn = QPushButton()
        augment_btn.clicked.connect(self.Augment)
        augment_btn.setIcon(QIcon(join(images_path + '/magic.png')))
        augment_btn.setIconSize(QSize(70, 70))
        augment_btn.setFixedWidth(100)
        augment_btn.setFixedHeight(90)
        augmentation_label = QLabel("        Augment", self)

        hbox_buttons = QHBoxLayout()
        vbox_rec = QVBoxLayout()
        vbox_rec.addWidget(reconstruct_btn)
        vbox_rec.addWidget(reconstruct_label)
        hbox_buttons.addLayout(vbox_rec)

        vbox_view = QVBoxLayout()
        vbox_view.addWidget(view_btn)
        vbox_view.addWidget(view_label)
        hbox_buttons.addLayout(vbox_view)

        vbox_comp = QVBoxLayout()
        vbox_comp.addWidget(segment_btn)
        vbox_comp.addWidget(segmentation_label)
        hbox_buttons.addLayout(vbox_comp)

        vbox_show = QVBoxLayout()
        vbox_show.addWidget(show_btn)
        vbox_show.addWidget(show_label)
        hbox_buttons.addLayout(vbox_show)

        vbox_aug = QVBoxLayout()
        vbox_aug.addWidget(augment_btn)
        vbox_aug.addWidget(augmentation_label)
        hbox_buttons.addLayout(vbox_aug)

        layout.addLayout(hbox_buttons, 3, 0, 1, 5)

        self.setLayout(layout)
        # set geometry
        self.setGeometry(0, 0, 1000, 650)
        self.setWindowTitle('App')
        self.show()

    def _viewReconstruction(self):
        draw_geometries([self.r.reconstructed_pointcloud])

    def style_choice(self, text):
        self.vid.frame_rate = int(text)

    def disable(self):
        self.sgnStop.emit(
        )  # send a queuedconnection type signal to the worker, because its in another thread

    def Reconstruct(self):
        self.r = Reconstructor()

    def Segment(self):
        self.s = Segmenter(self.r.reconstructed_pointcloud)

    def Augment(self):

        name = reconstructed_scene
        dummy_pcl = read_point_cloud(name)

        name_labels = segmented_reconstructed_scene
        dummy_pcl_labels = read_point_cloud(name_labels)

        t = Augmentor(dummy_pcl, dummy_pcl_labels)
        self.augmentation_ui = AugmentationUI(t)
Пример #48
0
class EditingTabWidget(Ui_EditingTabWidget, QObject):

    modeChangeSig = pyqtSignal(int)

    @pyqtSlot(bool)
    def showOnlySelected(self, doShow: bool):
        self.showSelected = doShow
        if self.mode == ConnectionMode:
            if self.graph != None:
                self.graph.setDisplayOnlySelectedComponents(self.showSelected)

    @pyqtSlot(int)
    def componentOneChanged(self, component: int):
        self.component1 = component
        if self.mode == ConnectionMode:
            if self.graph != None:
                self.graph.setComponent1(component)

    @pyqtSlot(int)
    def componentTwoChanged(self, component: int):
        self.component2 = component
        if self.mode == ConnectionMode:
            if self.graph != None:
                self.graph.setComponent2(component)

    @pyqtSlot(bool)
    def showBoundingBoxes(self, doShow: bool):
        self.showBoxes = doShow
        if self.mode == ConnectionMode:
            if self.graph != None:
                self.graph.setShowBoundingBoxes(self.showBoxes)

    @pyqtSlot(bool)
    def connectionModePressed(self, pressed: bool):
        self.changeMode(ConnectionMode)

    @pyqtSlot(bool)
    def acceptConnectionPressed(self, pressed: bool):
        if self.mode == ConnectionMode:
            self.graph.joinOperation()
            self.updateWidget()

    @pyqtSlot(bool)
    def breakModePressed(self, pressed: bool):
        self.changeMode(BreakMode)

    @pyqtSlot(bool)
    def removeComponentPressed(self, pressed: bool):
        self.changeMode(RemoveComponentMode)

    @pyqtSlot(bool)
    def splitEdgeModePressed(self, pressed: bool):
        self.changeMode(SplitEdgeMode)

    @pyqtSlot(bool)
    def acceptRemovalPressed(self, pressed: bool):
        if self.mode == BreakMode:
            if self.graph != None:
                self.graph.breakOperation()
                self.updateWidget()
        if self.mode == SplitEdgeMode:
            if self.graph != None:
                self.graph.splitOperation()
                self.updateWidget()
        # add code to c++
        if self.mode == RemoveComponentMode:
            if self.graph != None:
                print('enter c++ for remove component')
                self.graph.removeComponentOperation()
                self.updateWidget()

    def __init__(self, graphObject: mgraph, widget=None):
        Ui_EditingTabWidget.__init__(self)
        QObject.__init__(self)
        self.setupUi(widget)
        self.widget = widget
        self.graph = graphObject
        self.showSelected = False
        self.showBoxes = False
        self.mode = NoMode
        self.component1 = 0
        self.component2 = 0

        self.showOnlySelectedButton.toggled.connect(self.showOnlySelected)
        self.showBoundingBoxesButton.toggled.connect(self.showBoundingBoxes)
        self.ComponentOne.currentIndexChanged.connect(self.componentOneChanged)
        self.ComponentTwo.currentIndexChanged.connect(self.componentTwoChanged)
        self.ConnectionModeButton.clicked.connect(self.connectionModePressed)
        self.AcceptConnectionButton.clicked.connect(
            self.acceptConnectionPressed)
        self.BreakModeButton.clicked.connect(self.breakModePressed)
        self.SplitModeButton.clicked.connect(self.splitEdgeModePressed)
        self.AcceptRemovalButton.clicked.connect(self.acceptRemovalPressed)
        self.RemoveComponentButton.clicked.connect(self.removeComponentPressed)

    def changeMode(self, mode: int):
        if self.mode != mode or mode > 5:
            self.mode = mode
            print("changing mode")
            if self.graph != None:
                self.graph.unselectAll()
                self.updateWidget()
                if mode != ConnectionMode:
                    self.graph.setDisplayOnlySelectedComponents(False)
                    self.graph.setShowBoundingBoxes(False)
                if mode == ConnectionMode:
                    self.graph.setDisplayOnlySelectedComponents(
                        self.showSelected)
                    self.graph.setShowBoundingBoxes(self.showBoxes)
                self.graph.setDisplayStem(False)
                self.graph.setDisplayPrimaryNodes(False)
            self.modeChangeSig.emit(self.mode)

    def exitCurrentMode(self):
        pass

    def setGraph(self, graph: mgraph):
        print("setting graph")
        self.graph = graph
        self.updateWidget()
        if self.mode == ConnectionMode and self.graph != None:
            print("in setGraph")
            self.graph.setDisplayOnlySelectedComponents(self.showSelected)
            self.graph.setShowBoundingBoxes(self.showBoxes)

    def updateWidget(self):
        if self.graph != None:
            edgesString = str(self.graph.getNumEdgesToBreak())
            self.edgesToBreak.setText(edgesString)

            self.ComponentOne.currentIndexChanged.disconnect(
                self.componentOneChanged)
            self.ComponentTwo.currentIndexChanged.disconnect(
                self.componentTwoChanged)

            self.ComponentOne.clear()
            self.ComponentTwo.clear()

            componentSizes = self.graph.getComponentSizes()
            numComponents = self.graph.getNumComponents()
            for i in range(0, numComponents):
                descriptor = str(i) + ' - ' + str(round_to_2(
                    componentSizes[i]))
                self.ComponentOne.addItem(descriptor)
                self.ComponentTwo.addItem(descriptor)

            self.component1 = max(self.component1, 0)
            self.component2 = max(self.component2, 0)
            self.component1 = min(self.component1, numComponents - 1)
            self.component2 = min(self.component2, numComponents - 1)

            self.ComponentOne.setCurrentIndex(self.component1)
            self.ComponentTwo.setCurrentIndex(self.component2)
            self.graph.setComponent1(self.component1)
            self.graph.setComponent2(self.component2)

            self.ComponentOne.currentIndexChanged.connect(
                self.componentOneChanged)
            self.ComponentTwo.currentIndexChanged.connect(
                self.componentTwoChanged)

        else:
            self.edgesToBreak.setText("")
            self.ComponentOne.clear()
            self.ComponentTwo.clear()