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)
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)
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)
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()
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()))
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
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)
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 ))
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
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
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)
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)
# 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):
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)
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
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)
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
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)
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)
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 ''
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)
def mouseReleaseEvent(self, ev): self.emit(pyqtSignal('clicked()')) return
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
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()
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)
class SIRTrackerSignals(QObject): status_changed = pyqtSignal(int, str) frame_changed = pyqtSignal(dict) template_changed = pyqtSignal(object) finished = pyqtSignal(int)
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|¶ms[^&]*', '', 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)
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))
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})
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")
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()
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"
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()
class TOS(QTextEdit): tos_signal = pyqtSignal() error_signal = pyqtSignal(object)
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())
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())
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()
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
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)
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()