class PlotSignal(QObject): plot = Signal()
class MainWindow(QMainWindow, Ui_MainWindow): update = Signal() def __init__(self, parent=None): super(MainWindow, self).__init__(parent) self.setupUi(self) self.loadSettings() self.mainPokemon.setTitle("Main Pokemon") self.eggParent1.setTitle("Parent 1") self.eggParent2.setTitle("Parent 2") self.sosPokemon.setTitle("SOS Pokemon") self.pushButtonConnect.clicked.connect(self.connectCitra) self.pushButtonDisconnect.clicked.connect(self.disconnectCitra) self.doubleSpinBoxDelay.valueChanged.connect(self.updateDelay) self.pushButtonMainUpdate.clicked.connect(self.toggleMainRNG) self.pushButtonEggUpdate.clicked.connect(self.toggleEggRNG) self.pushButtonSOSUpdate.clicked.connect(self.toggleSOSRNG) self.pushButtonSOSReset.clicked.connect(self.resetSOSRNG) self.mainPokemon.pushButtonUpdate.clicked.connect( self.updateMainPokemon) self.eggParent1.pushButtonUpdate.clicked.connect(self.updateEggParent1) self.eggParent2.pushButtonUpdate.clicked.connect(self.updateEggParent2) self.sosPokemon.pushButtonUpdate.clicked.connect(self.updateSOSPokemon) self.update.connect(self.updateMainRNG) self.update.connect(self.updateEggRNG) self.update.connect(self.updateSOSRNG) def closeEvent(self, event): self.saveSettings() #return super().closeEvent(event) def saveSettings(self): settings = QSettings() settings.setValue("delay", self.doubleSpinBoxDelay.value()) def loadSettings(self): settings = QSettings() self.delay = float(settings.value("delay", 0.5)) self.doubleSpinBoxDelay.setValue(self.delay) def connectCitra(self): index = self.comboBoxGameSelection.currentIndex() if index == 0: self.manager = ManagerSM() else: self.manager = ManagerUSUM() seed = self.manager.readMainInitialSeed() if seed == 0: message = QMessageBox() message.setText( "Initial seed not valid.\nCheck that you are using the correct game or the latest version of the game" ) message.exec_() self.manager = None return self.allowUpdate = True self.toggleEnable(True) self.labelStatus.setText("Connected") self.pushButtonConnect.setEnabled(False) self.pushButtonDisconnect.setEnabled(True) self.mainRNG = False self.eggRNG = False self.sosRNG = False t = threading.Thread(target=self.autoUpdate) time.sleep(1) t.start() def disconnectCitra(self): self.allowUpdate = False self.manager = None self.toggleEnable(False) self.pushButtonConnect.setEnabled(True) self.pushButtonDisconnect.setEnabled(False) self.pushButtonMainUpdate.setText("Update") self.pushButtonEggUpdate.setText("Update") self.pushButtonSOSUpdate.setText("Update") self.labelStatus.setText("Disconnected") def toggleEnable(self, flag): self.comboBoxMainIndex.setEnabled(flag) self.comboBoxSOSIndex.setEnabled(flag) self.doubleSpinBoxDelay.setEnabled(flag) self.mainPokemon.pushButtonUpdate.setEnabled(flag) self.eggParent1.pushButtonUpdate.setEnabled(flag) self.eggParent2.pushButtonUpdate.setEnabled(flag) self.sosPokemon.pushButtonUpdate.setEnabled(flag) self.pushButtonMainUpdate.setEnabled(flag) self.pushButtonEggUpdate.setEnabled(flag) self.pushButtonSOSUpdate.setEnabled(flag) self.pushButtonSOSReset.setEnabled(flag) @Slot() def updateMainRNG(self): if self.mainRNG: values = self.manager.updateMainFrameCount() # Handle infinite loop if values is None: message = QMessageBox() message.setText( "Exiting an infinite loop. Make sure no patches are installed and the game is on the latest version" ) message.exec_() self.toggleMainRNG() return # Check to see if frame changed at all if values[0] == 0: return self.lineEditMainInitialSeed.setText(hexify(values[1])) self.lineEditMainCurrentSeed.setText(hexify(values[2])) self.lineEditMainFrame.setText(str(values[3])) self.lineEditMainTSV.setText(str(values[4])) def toggleMainRNG(self): if self.pushButtonMainUpdate.text() == "Update": self.mainRNG = True self.pushButtonMainUpdate.setText("Pause") else: self.mainRNG = False self.pushButtonMainUpdate.setText("Update") @Slot() def updateEggRNG(self): if self.eggRNG: values = self.manager.eggStatus() if values[0] == 0: self.labelEggReadyStatus.setText("No egg yet") else: self.labelEggReadyStatus.setText("Egg ready") self.lineEditEggSeed3.setText(hexify(values[1])) self.lineEditEggSeed2.setText(hexify(values[2])) self.lineEditEggSeed1.setText(hexify(values[3])) self.lineEditEggSeed0.setText(hexify(values[4])) def toggleEggRNG(self): if self.pushButtonEggUpdate.text() == "Update": self.eggRNG = True self.pushButtonEggUpdate.setText("Pause") else: self.eggRNG = False self.pushButtonEggUpdate.setText("Update") @Slot() def updateSOSRNG(self): if self.sosRNG: if self.manager.sosInitialSeed is None: self.manager.readSOSInitialSeed() values = self.manager.updateSOSFrameCount() # Handle infinite loop if values is None: message = QMessageBox() message.setText( "Exiting an infinite loop. Retry the battle and start updating before taking any actions." ) message.exec_() self.toggleSOSRNG() return # Check to see if frame changed at all if values[0] == 0: return self.lineEditSOSInitialSeed.setText(hexify(values[1])) self.lineEditSOSCurrentSeed.setText(hexify(values[2])) self.lineEditSOSFrame.setText(str(values[3])) self.lineEditSOSChainCount.setText(str(values[4])) def toggleSOSRNG(self): if self.pushButtonSOSUpdate.text() == "Update": self.sosRNG = True self.pushButtonSOSUpdate.setText("Pause") else: self.sosRNG = False self.pushButtonSOSUpdate.setText("Update") @Slot() def resetSOSRNG(self): self.manager.sosInitialSeed = None def updateMainPokemon(self): index = self.comboBoxMainIndex.currentIndex() if index < 6: pkm = self.manager.partyPokemon(index) else: pkm = self.manager.wildPokemon() self.mainPokemon.updateInformation(pkm) def updateEggParent1(self): pkm = self.manager.getParent(1) self.eggParent1.updateInformation(pkm) def updateEggParent2(self): pkm = self.manager.getParent(2) self.eggParent2.updateInformation(pkm) def updateSOSPokemon(self): index = self.comboBoxSOSIndex.currentIndex() pkm = self.manager.sosPokemon(index) self.sosPokemon.updateInformation(pkm) def updateDelay(self): val = self.doubleSpinBoxDelay.value() self.delay = val def autoUpdate(self): while self.allowUpdate and self.manager.citra.is_connected(): self.update.emit() time.sleep(self.delay)
class PuppetMaster(QMainWindow): requestEditMode = Signal(bool) def __init__(self, parent=None, editMode=bool(False)): super(PuppetMaster, self).__init__(parent) self._parent = parent self._model = list() self.editMode = editMode self.setMouseTracking(True) self.setFocusPolicy(Qt.StrongFocus) self.setWindowTitle('PuppetMaster') self.setMinimumHeight(1) self.setMinimumWidth(1) self.setContextMenuPolicy(Qt.PreventContextMenu) # Main tabs self.tab = CanvasGraphicsViewTab(parent=self) self.tab.requestEditMode.connect(self.set_edit) self.setCentralWidget(self.tab) # Parameter Widget self.parameter = Parameters(parent=self) self.parameterDock = QDockWidget(": Parameters ::", self) self.parameterDock.setObjectName('Parameters') self.parameterDock.setFeatures(QDockWidget.DockWidgetClosable) self.parameterDock.setWidget(self.parameter) self.parameterDock.setTitleBarWidget(QWidget(self.parameterDock)) self.parameterDock.setVisible(self.editMode) self.addDockWidget(Qt.BottomDockWidgetArea, self.parameterDock) self.tab.onSelection.connect(self.update_parameters) self.parameter.onChangeBGColor.connect(self.tab.update_bg_color) self.parameter.onChangeFontColor.connect(self.tab.update_font_color) self.parameter.onChangeFontSize.connect(self.tab.update_font_size) self.parameter.onChangeText.connect(self.tab.update_text) self.parameter.onChangeShape.connect(self.tab.update_shape) # maya Signal on selection items self.idx = OpenMaya.MEventMessage.addEventCallback( "SelectionChanged", self.tab.maya_selection) # Menu self.setMenuBar(self.create_menu()) self.set_edit(self.editMode) def update_parameters(self, node=PickNode): shape = node.Shape if isinstance(node, PickNode) else str() self.parameter.update_param(text=node.toPlainText(), fontSize=node.font().pointSize(), fontColor=node.defaultTextColor(), bgColor=node.Background, shapeName=shape) def create_menu(self): window_menu = QMenuBar(self) file_menu = window_menu.addMenu("&File") new_action = QAction("&New...", self) new_action.setShortcut('Ctrl+N') new_action.setShortcutContext(Qt.WidgetShortcut) new_action.setStatusTip('Create a new Set') new_action.triggered.connect(self.tab.new_tab) file_menu.addAction(new_action) open_action = QAction("&Open...", self) open_action.setShortcut('Ctrl+O') open_action.setShortcutContext(Qt.WidgetShortcut) open_action.setStatusTip('Open a new set') open_action.triggered.connect(self.tab.open_set) file_menu.addAction(open_action) refresh_action = QAction("&Refresh...", self) refresh_action.setStatusTip('Refresh the current sets') refresh_action.triggered.connect(self.tab.refresh_set) file_menu.addAction(refresh_action) file_menu.addSeparator() save_action = QAction("&Save...", self) save_action.setShortcut('Ctrl+S') save_action.setShortcutContext(Qt.WidgetShortcut) save_action.setStatusTip('Save the current tab') save_action.triggered.connect(self.tab.save_set) file_menu.addAction(save_action) saveAs_action = QAction("&Save As...", self) saveAs_action.setShortcut('Ctrl+Shift+S') saveAs_action.setShortcutContext(Qt.WidgetShortcut) saveAs_action.setStatusTip('Save the current tab as a new Set') saveAs_action.triggered.connect(self.tab.saveAs_set) file_menu.addAction(saveAs_action) saveAsTemp_action = QAction("&Save As Template...", self) saveAsTemp_action.setStatusTip( 'Save the current tab as a new Template') saveAsTemp_action.triggered.connect(self.tab.saveAsTemplate_set) file_menu.addAction(saveAsTemp_action) file_menu.addSeparator() rename_action = QAction("&Rename Tab...", self) rename_action.setStatusTip('Rename the current Tab') rename_action.triggered.connect(self.tab.rename_set) file_menu.addAction(rename_action) close_action = QAction("&Close Tab...", self) close_action.setShortcut('Ctrl+Shift+W') close_action.setShortcutContext(Qt.WidgetShortcut) close_action.setStatusTip('Close the current Tab') close_action.triggered.connect(self.tab.closeCurrentTab) file_menu.addAction(close_action) picker_menu = window_menu.addMenu("&Picker") self.edit_action = QAction("&Edit Mode", self) self.edit_action.setStatusTip('Toggle between view and edit mode.') self.edit_action.triggered.connect(self.edit_toggle) self.edit_action.setCheckable(True) self.edit_action.setChecked(self.editMode) picker_menu.addAction(self.edit_action) picker_menu.addSeparator() self.background_action = QAction("&Change Background...", self) self.background_action.setEnabled(self.editMode) self.background_action.setStatusTip('Change the background.') self.background_action.triggered.connect(self.tab.set_background) picker_menu.addAction(self.background_action) self.namespace_action = QAction("&Change Namespace...", self) self.namespace_action.setEnabled(self.editMode) self.namespace_action.setStatusTip('Change the namespace.') self.namespace_action.triggered.connect(self.tab.set_namespace) picker_menu.addAction(self.namespace_action) help_menu = window_menu.addMenu("&Help") wiki_action = QAction("&About PuppetMaster...", self) wiki_action.setStatusTip('Open Wiki page') wiki_action.triggered.connect(self.wiki_open) help_menu.addAction(wiki_action) return window_menu def edit_toggle(self): self.set_edit(not self.editMode) def wiki_open(self): webbrowser.open_new_tab( 'https://github.com/Bernardrouhi/PuppetMaster/wiki') def force_load(self, paths=list): self.tab.force_load(paths) def findAndLoad(self, names=list()): ''' Find the names in PuppetMaster folder and load them Parameters ---------- names: (list) List of dictionaries of name and namespace. ''' if names: self.tab.findAndLoad(names) def destroyMayaSignals(self): # destroy the connection OpenMaya.MMessage.removeCallback(self.idx) def get_edit(self): return self.editMode def set_edit(self, value=bool): self.editMode = value self.background_action.setEnabled(self.editMode) self.namespace_action.setEnabled(self.editMode) self.parameterDock.setVisible(self.editMode) self.edit_action.setChecked(self.editMode) self.tab.Edit = self.editMode # Notification if not self.editMode: warningMes( "PUPPETMASTER-INFO: Out of 'Edit Mode' you won't be able to create/edit/move or delete any node." ) else: warningMes( "PUPPETMASTER-INFO: In 'Edit Mode' command buttons won't run any commands." ) Edit = property(get_edit, set_edit) def keyPressEvent(self, event): if event.modifiers() == (Qt.ControlModifier | Qt.ShiftModifier): if event.key() == Qt.Key_W: self.tab.closeCurrentTab() elif event.key() == Qt.Key_S: self.tab.saveAs_set() elif event.modifiers() == Qt.ControlModifier: if event.key() == Qt.Key_S: self.tab.save_set() elif event.key() == Qt.Key_N: self.tab.new_tab() elif event.key() == Qt.Key_O: self.tab.open_set() else: super(PuppetMaster, self).keyPressEvent(event)
class ImportDialog(QDialog): """ A widget for importing data into a Spine db. Currently used by DataStoreForm. It embeds three widgets that alternate depending on user's actions: - `select_widget` is a `QWidget` for selecting the source data type (CSV, Excel, etc.) - `_import_preview` is an `ImportPreviewWidget` for defining Mappings to associate with the source data - `_error_widget` is an `ImportErrorWidget` to show errors from import operations """ data_ready = Signal(dict) _SETTINGS_GROUP_NAME = "importDialog" def __init__(self, settings, parent): """ Args: settings (QSettings): settings for storing/restoring window state parent (DataStoreForm): parent widget """ from ...ui.import_source_selector import Ui_ImportSourceSelector # pylint: disable=import-outside-toplevel super().__init__(parent) self.setWindowFlag(Qt.Window, True) self.setWindowFlag(Qt.WindowMinMaxButtonsHint, True) self.setWindowTitle("Import data") # state self._mapped_data = None self._mapping_errors = [] connector_list = [ CSVConnector, ExcelConnector, SqlAlchemyConnector, GdxConnector, JSONConnector ] self.connectors = {c.DISPLAY_NAME: c for c in connector_list} self._selected_connector = None self.active_connector = None self._current_view = "connector" self._settings = settings self._preview_window_state = {} # create widgets self._import_preview = None self._error_widget = ImportErrorsWidget() self._error_widget.hide() self._dialog_buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Abort | QDialogButtonBox.Cancel) self._dialog_buttons.button(QDialogButtonBox.Abort).setText("Back") self._layout = QVBoxLayout() # layout self.select_widget = QWidget(self) self._select_widget_ui = Ui_ImportSourceSelector() self._select_widget_ui.setupUi(self.select_widget) self.setLayout(QVBoxLayout()) self.layout().addLayout(self._layout) self.layout().addWidget(self._dialog_buttons) self._layout.addWidget(self._error_widget) self._layout.addWidget(self.select_widget) # set list items self._select_widget_ui.source_list.blockSignals(True) self._select_widget_ui.source_list.addItems( list(self.connectors.keys())) self._select_widget_ui.source_list.clearSelection() self._select_widget_ui.source_list.blockSignals(False) # connect signals self._select_widget_ui.source_list.currentItemChanged.connect( self.connector_selected) self._select_widget_ui.source_list.activated.connect( self.launch_import_preview) self._dialog_buttons.button(QDialogButtonBox.Ok).clicked.connect( self._handle_ok_clicked) self._dialog_buttons.button(QDialogButtonBox.Cancel).clicked.connect( self._handle_cancel_clicked) self._dialog_buttons.button(QDialogButtonBox.Abort).clicked.connect( self._handle_back_clicked) self.data_ready.connect(self.parent().import_data) # init ok button self.set_ok_button_availability() self._dialog_buttons.button(QDialogButtonBox.Abort).hide() @property def mapped_data(self): return self._mapped_data @property def mapping_errors(self): return self._mapping_errors @Slot("QListWidgetItem", "QListWidgetItem") def connector_selected(self, current, _previous): connector = None if current: connector = self.connectors.get(current.text(), None) self._selected_connector = connector self.set_ok_button_availability() def set_ok_button_availability(self): if self._current_view == "connector": if self._selected_connector: self._dialog_buttons.button( QDialogButtonBox.Ok).setEnabled(True) else: self._dialog_buttons.button( QDialogButtonBox.Ok).setEnabled(False) elif self._current_view == "preview": if self._import_preview.checked_tables: self._dialog_buttons.button( QDialogButtonBox.Ok).setEnabled(True) else: self._dialog_buttons.button( QDialogButtonBox.Ok).setEnabled(False) else: self._dialog_buttons.button(QDialogButtonBox.Ok).setEnabled(True) @Slot(dict, list) def _handle_data_ready(self, data, errors): if errors: errors = [ f"{table_name}: {error_message}" for table_name, error_message in errors ] self._error_widget.set_import_state(errors) self.set_error_widget_as_main_widget() return self.data_ready.emit(data) self.accept() @Slot() def _handle_ok_clicked(self): if self._current_view == "connector": self.launch_import_preview() elif self._current_view == "preview": self._import_preview.request_mapped_data() elif self._current_view == "error": self.accept() @Slot() def _handle_cancel_clicked(self): self.reject() @Slot() def _handle_back_clicked(self): self.set_preview_as_main_widget() @Slot() def launch_import_preview(self): if self._selected_connector: # create instance of connector connector_settings = { "gams_directory": self._gams_system_directory() } self.active_connector = ConnectionManager(self._selected_connector, connector_settings) valid_source = self.active_connector.connection_ui() if valid_source: # Create instance of ImportPreviewWidget and configure self._import_preview = ImportEditor(self.active_connector, self) self._import_preview.set_loading_status(True) self._import_preview.tableChecked.connect( self.set_ok_button_availability) # Connect handle_data_ready method to the widget self._import_preview.mappedDataReady.connect( self._handle_data_ready) self._layout.addWidget(self._import_preview) self.active_connector.connectionFailed.connect( self._handle_failed_connection) self.active_connector.connectionReady.connect( self.set_preview_as_main_widget) self.active_connector.init_connection() else: # remove connector object. self.active_connector.deleteLater() self.active_connector = None @Slot(str) def _handle_failed_connection(self, msg): """Handle failed connection, show error message and select widget Arguments: msg {str} -- str with message of reason for failed connection. """ if self.active_connector: self.active_connector.close_connection() self.active_connector.deleteLater() self.active_connector = None if self._import_preview: self._import_preview.deleteLater() self._import_preview = None notification = Notification(self, msg) notification.show() @Slot() def set_preview_as_main_widget(self): self._current_view = "preview" self.select_widget.hide() self._error_widget.hide() self._import_preview.show() self._restore_preview_ui() self._dialog_buttons.button(QDialogButtonBox.Abort).hide() self.set_ok_button_availability() def set_error_widget_as_main_widget(self): self._current_view = "error" if self._import_preview is not None and not self._import_preview.isHidden( ): self._preview_window_state["maximized"] = self.windowState( ) == Qt.WindowMaximized self._preview_window_state["size"] = self.size() self._preview_window_state["position"] = self.pos() splitters = dict() self._preview_window_state["splitters"] = splitters for splitter in self._import_preview.findChildren(QSplitter): splitters[splitter.objectName()] = splitter.saveState() self.select_widget.hide() self._error_widget.show() self._import_preview.hide() self._dialog_buttons.button(QDialogButtonBox.Abort).show() self.set_ok_button_availability() def _restore_preview_ui(self): """Restore UI state from previous session.""" if not self._preview_window_state: self._settings.beginGroup(self._SETTINGS_GROUP_NAME) window_size = self._settings.value("windowSize") window_pos = self._settings.value("windowPosition") n_screens = self._settings.value("n_screens", defaultValue=1) window_maximized = self._settings.value("windowMaximized", defaultValue='false') splitter_state = {} for splitter in self._import_preview.findChildren(QSplitter): splitter_state[splitter] = self._settings.value( splitter.objectName() + "_splitterState") self._settings.endGroup() original_size = self.size() if window_size: self.resize(window_size) else: self.setGeometry( QStyle.alignedRect( Qt.LeftToRight, Qt.AlignCenter, QSize(1000, 700), QApplication.desktop().availableGeometry(self))) if window_pos: self.move(window_pos) if len(QGuiApplication.screens()) < int(n_screens): # There are less screens available now than on previous application startup self.move( 0, 0) # Move this widget to primary screen position (0,0) ensure_window_is_on_screen(self, original_size) if window_maximized == 'true': self.setWindowState(Qt.WindowMaximized) for splitter, state in splitter_state.items(): if state: splitter.restoreState(state) else: self.resize(self._preview_window_state["size"]) self.move(self._preview_window_state["position"]) self.setWindowState(self._preview_window_state["maximized"]) for splitter in self._import_preview.findChildren(QSplitter): name = splitter.objectName() splitter.restoreState( self._preview_window_state["splitters"][name]) def closeEvent(self, event): """Stores window's settings and accepts the event.""" if self._import_preview is not None: self._settings.beginGroup(self._SETTINGS_GROUP_NAME) self._settings.setValue("n_screens", len(QGuiApplication.screens())) if not self._import_preview.isHidden(): self._settings.setValue("windowSize", self.size()) self._settings.setValue("windowPosition", self.pos()) self._settings.setValue( "windowMaximized", self.windowState() == Qt.WindowMaximized) for splitter in self._import_preview.findChildren(QSplitter): self._settings.setValue( splitter.objectName() + "_splitterState", splitter.saveState()) elif self._preview_window_state: self._settings.setValue("windowSize", self._preview_window_state["size"]) self._settings.setValue("windowPosition", self._preview_window_state["position"]) self._settings.setValue("windowMaximized", self._preview_window_state.maximized) for name, state in self._preview_window_state["splitters"]: self._settings.setValue(name + "_splitterState", state) self._settings.endGroup() event.accept() def _gams_system_directory(self): """Returns GAMS system path from Toolbox settings or None if GAMS default is to be used.""" path = self._settings.value("appSettings/gamsPath", defaultValue=None) if not path: path = find_gams_directory() if path is not None and os.path.isfile(path): path = os.path.dirname(path) return path
class Host(QObject): '''A representation of a remote host and a connection to it. ''' runs_updated = Signal(dict) def __init__(self, pattern_parser, connection, datastore): ''' Parameters ---------- `pattern_parser` : rynner PatternParser See :class:`PatternParser<rynner.pattern_parser.PatternParser>`. `connection` : rynner Connection See :class:`Connection <rynner.host.Connection>`. `datastore` : rynner Datastore See :class:`Datastore<rynner.datastore.Datastore>`. ''' self.connection = connection self.pattern_parser = pattern_parser self.datastore = datastore self._cached_runs = {} #NoUT super().__init__(parent=None) def upload(self, plugin_id, run_id, uploads): ''' Uploads files using the connection to the remote host. Parameters ---------- `plugin_id` : Plugin identifier (see :func:`Plugin constructor<rynner.plugin.Plugin.__init__>`) ''' for upload in uploads: if len(upload) != 2: raise InvalidContextOption( 'invalid format for uploads options: {uploads}') local, remote = upload basedir = self._remote_basedir(plugin_id, run_id) basedir = os.path.join(basedir, remote) self.connection.put_file(local, remote) def parse(self, plugin_id, run_id, options): ''' Ask pattern_parser to build a context object from the options supplied by calls to :class:`~rynner.run.RunManager`. ''' context = self.pattern_parser.parse(options) return context def run(self, plugin_id, run_id, context): ''' Run a job for a context, which was returned previously from a call to self.parse. Details of creating a job on a remote machine according to the context object is delegated to pattern_parser object. ''' exit_status = self.pattern_parser.run( self.connection, context, self._remote_basedir(plugin_id, run_id)) def store(self, plugin_id, run_id, data): ''' Persists data for a run using the datastore object ''' basedir = self._remote_basedir(plugin_id, run_id) self.datastore.write(basedir, data) def _remote_basedir(self, plugin_id, run_id=''): ''' Returns the remote base directory. Typically a working directory of a run on the remote filesystem. ''' return os.path.join("rynner", plugin_id, run_id) def type(self, string): ''' Gets type from this pattern_parser and returns it. ''' return self.pattern_parser.type(string) def runs(self, plugin_id): ''' Uses the datastore to return a list of all data for jobs for a given plugin. ''' if plugin_id in self._cached_runs.keys(): return self._cached_runs[plugin_id] else: return [] def update(self, plugin_id): ''' Triggers an update of job data from datastore ''' basedir = self._remote_basedir(plugin_id) all_ids = self.datastore.all_job_ids(basedir) new_ids = { i: self._remote_basedir(plugin_id, i) for i in all_ids if i not in self._cached_runs.keys() } new_runs = self.datastore.read_multiple(new_ids) # update cache with new runs if plugin_id not in self._cached_runs.keys(): self._cached_runs[plugin_id] = {} self._cached_runs[plugin_id].update(new_runs) # get queue qids = [data['qid'] for data in self._cached_runs[plugin_id].values()] queue = self.get_queue(qids) # merge queue info into all cached run data for run_data in self._cached_runs[plugin_id].values(): qid = run_data['qid'] if qid in queue.keys(): run_data['queue'] = queue[qid] self.runs_updated.emit(self._cached_runs) def get_queue(self, jids): raise NotImplementedError( "method get_queue should be implemented in subclass")
class FE14MapCell(QLabel): selected_for_move = Signal(dict) spawn_selected = Signal(dict) tile_selected = Signal(dict) spawn_dragged_over = Signal(int, int) def __init__(self, row, column): super().__init__() self.setAlignment(QtGui.Qt.AlignCenter) self.setAcceptDrops(True) self.row = row self.column = column self.spawns = [] self.terrain_mode = False self._chapter_data = None self._current_color = "#424242" self._current_border = DEFAULT_BORDER self.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) self.setFixedSize(32, 32) self._refresh_stylesheet() def set_color(self, color_style_string): self._current_color = color_style_string self._refresh_stylesheet() def set_border(self, border): self._current_border = border self._refresh_stylesheet() def _refresh_stylesheet(self): params = (self._current_border, self._current_color) self.setStyleSheet("QLabel { border: %s; background-color: %s }" % params) def place_spawn(self, spawn): self.spawns.append(spawn) self._set_occupation_from_last_spawn() def pop_spawn(self): result = self.spawns.pop() self._set_occupation_from_last_spawn() return result def remove_spawn(self, spawn): if spawn in self.spawns: self.spawns.remove(spawn) self._set_occupation_from_last_spawn() def clear_spawns(self): self.spawns.clear() self.clear() self.set_border(DEFAULT_BORDER) def set_selected(self, is_selected): if is_selected: self.set_border(SELECTED_BORDER) else: self.set_border(DEFAULT_BORDER) def _set_occupation_from_last_spawn(self): if not self.spawns: self.clear() return last_spawn = self.spawns[-1] pixmap = self._get_pixmap_from_spawn(last_spawn) self.setPixmap(pixmap) def dragEnterEvent(self, event: QDragEnterEvent): if not self.terrain_mode and event.mimeData().hasFormat( "application/fe14-spawn"): self.spawn_dragged_over.emit(self.row, self.column) event.acceptProposedAction() else: event.ignore() def dragMoveEvent(self, event: QDragMoveEvent): if not self.terrain_mode and event.mimeData().hasFormat( "application/fe14-spawn"): event.acceptProposedAction() else: event.ignore() def dropEvent(self, event: QDropEvent): if not self.terrain_mode and event.mimeData().hasFormat( "application/fe14-spawn"): event.acceptProposedAction() else: event.ignore() def mousePressEvent(self, ev: QMouseEvent): if self.terrain_mode: self.tile_selected.emit(self) else: self._handle_mouse_press_event_for_dispos(ev) def _handle_mouse_press_event_for_dispos(self, ev: QMouseEvent): if ev.button() != QtCore.Qt.LeftButton: return if self.spawns: self.spawn_selected.emit(self) mime_data = QMimeData() mime_data.setData("application/fe14-spawn", b"") drag = QDrag(self) drag.setMimeData(mime_data) drag.setHotSpot(ev.pos()) drag.exec_(QtCore.Qt.MoveAction) def transition_to_terrain_mode(self): self.terrain_mode = True self.set_border(DEFAULT_BORDER) def transition_to_dispos_mode(self): self.terrain_mode = False def update_chapter_data(self, chapter_data): self._chapter_data = chapter_data @staticmethod def _get_default_pixmap_by_team(team) -> QPixmap: if team == 0: occupation_state = MapCellOccupationState.PLAYER elif team == 1: occupation_state = MapCellOccupationState.ENEMY else: occupation_state = MapCellOccupationState.ALLIED return QPixmap(_OCCUPATION_PIXMAPS[occupation_state]) def _get_pixmap_from_spawn(self, spawn): sprite_service = locator.get_scoped("SpriteService") pid = spawn["PID"].value team = spawn["Team"].value sprite = None if self._chapter_data and self._chapter_data.person: person = self._chapter_data.person element = person.get_element_by_key(pid) if element: sprite = sprite_service.get_sprite_for_character(element, team) if not sprite: characters = locator.get_scoped("ModuleService").get_module( "Characters") element = characters.get_element_by_key(pid) if element: sprite = sprite_service.get_sprite_for_character(element, team) if not sprite: sprite = self._get_default_pixmap_by_team(team) return sprite
class MainWindow(QMainWindow): img = None processed_img = None pdf = None pdf_image_refs = [] pdf_image_idx = -1 pixmap = None qr_code_id = None variant = 0 valid_answers = {} score = -1.0 updateWidgetsState = Signal() updateImageView = Signal() updateSheetResults = Signal() settings = None template_filename = None grading_script_filename = None saved_grading_script_filename = None student_records_filename = None saved_student_records_filename = None student_records = [] question_variants = [] answers_variants = [] answer_scores = [] recent_files = [] recent_files_actions = [] max_recent_files = 5 def __init__(self): super(MainWindow, self).__init__() self.ui = Ui_MainWindow() self.ui.setupUi(self) self.ui.action_Open_image.triggered.connect(self.openImage) self.ui.action_Open_PDF_collection.triggered.connect(self.openPDF) self.ui.action_Set_template_file.triggered.connect( self.setTemplateFile) self.ui.action_Set_grading_R_file.triggered.connect( self.setGradingScriptFile) self.ui.action_Set_student_records_file.triggered.connect( self.setStudentRecordsFile) self.ui.nextButton.clicked.connect(self.nextPdfImage) self.ui.prevButton.clicked.connect(self.prevPdfImage) self.updateWidgetsState.connect(self.onUpdateWidgetsState) self.updateImageView.connect(self.onUpdateImageView) self.updateSheetResults.connect(self.onUpdateSheetResults) self.ui.processButton.clicked.connect(self.processImage) self.ui.saveResultButton.clicked.connect(self.saveResult) self.settings = QSettings() if self.settings.contains("template_file"): self.template_filename = self.settings.value("template_file") if self.settings.contains("student_records_file"): self.saved_student_records_filename = self.settings.value( "student_records_file") if self.settings.contains("grading_script_file"): self.saved_grading_script_filename = self.settings.value( "grading_script_file") if self.settings.contains("recent_files"): try: self.recent_files = json.loads( self.settings.value("recent_files")) except (ValueError, TypeError): self.recent_files = [] self.updateWidgetsState.emit() def resizeEvent(self, event): self.updateImageView.emit() return super(MainWindow, self).resizeEvent(event) def resetSheetResults(self): self.processed_img = None self.pixmap = None self.qr_code_id = None self.variant = 0 self.valid_answers = {} self.score = -1 def updatePdfSelection(self): self.resetSheetResults() self.img = fitz_to_cv(self.pdf, self.pdf_image_refs[self.pdf_image_idx]) self.pixmap = cv_to_qpixmap(cv.cvtColor(self.img, cv.COLOR_BGR2RGB)) self.updateWidgetsState.emit() self.updateImageView.emit() self.updateSheetResults.emit() def updateRecentFiles(self, filename): try: self.recent_files.remove(filename) except ValueError: pass if len(self.recent_files) > self.max_recent_files: self.recent_files = self.recent_files[1:] self.recent_files.append(filename) self.settings.setValue('recent_files', json.dumps(self.recent_files)) def loadImage(self, filename): if os.path.isfile(filename): self.img = cv.imread(filename) self.updateRecentFiles(filename) self.pdf = None self.pdf_image_refs = [] self.pdf_image_idx = -1 self.resetSheetResults() self.updateWidgetsState.emit() self.pixmap = cv_to_qpixmap(cv.cvtColor(self.img, cv.COLOR_BGR2RGB)) self.updateImageView.emit() self.updateSheetResults.emit() def loadPDF(self, filename): if os.path.isfile(filename): self.pdf = fitz.open(filename) self.updateRecentFiles(filename) self.pdf_image_refs = [] for p in range(len(self.pdf)): self.pdf_image_refs += [ obj[0] for obj in self.pdf.getPageImageList(p) ] self.pdf_image_idx = 0 self.updatePdfSelection() def computeScore(self, variant, answers, question_variants, answers_variants, answer_scores): choice_to_int = {"a": 0, "b": 1, "c": 2, "d": 3} score = 0.0 for q, a in answers.items(): orig_q = question_variants[variant - 1].index(q) orig_a = answers_variants[variant - 1][orig_q][choice_to_int[a.lower()]] - 1 score += answer_scores[orig_q][orig_a] if answer_scores[orig_q][orig_a] < 0.1: print("Wrong answer for question {:d} in variant {:d}".format( q, variant)) return score @Slot() def openImage(self): filename, _ = QFileDialog.getOpenFileName( self, "Open Image", "", "Image Files (*.png *.jpg *.bmp)") self.loadImage(filename) @Slot() def openPDF(self): filename, _ = QFileDialog.getOpenFileName(self, "Open PDF collection", "", "PDF Files (*.pdf)") self.loadPDF(filename) @Slot() def openRecent(self): action = self.sender() if action: filename = action.data() _, extension = os.path.splitext(filename) if extension.lower() == ".pdf": self.loadPDF(filename) else: self.loadImage(filename) @Slot() def setTemplateFile(self): filename, _ = QFileDialog.getOpenFileName( self, "Set answer sheet template file", "", "SVG Template Files (*.svg)") if os.path.isfile(filename): self.template_filename = filename self.settings.setValue("template_file", self.template_filename) self.updateWidgetsState.emit() @Slot() def setGradingScriptFile(self): filename, _ = QFileDialog.getOpenFileName( self, "Set R grading file", self.saved_grading_script_filename, "R Script Files (*.r)") if os.path.isfile(filename): self.grading_script_filename = filename self.saved_grading_script_filename = filename self.settings.setValue("grading_script_file", self.grading_script_filename) @Slot() def setStudentRecordsFile(self): filename, _ = QFileDialog.getOpenFileName( self, "Set student records file", self.saved_student_records_filename, "CSV Files (*.csv)") if os.path.isfile(filename): self.student_records_filename = filename self.saved_student_records_filename = filename self.settings.setValue("student_records_file", self.student_records_filename) with open(self.student_records_filename) as csvfile: records = csv.reader(csvfile, delimiter=",", quotechar='"') self.student_records = [] for row in records: while len(row) < 4: row.append("") if row[2] == "": row[2] = "0" if row[3] == "": row[3] = "-1" self.student_records.append(row) csvfile.close() self.updateWidgetsState.emit() @Slot() def nextPdfImage(self): self.pdf_image_idx += 1 self.updatePdfSelection() @Slot() def prevPdfImage(self): self.pdf_image_idx -= 1 self.updatePdfSelection() @Slot() def processImage(self): if self.img is not None: if self.grading_script_filename is None: self.setGradingScriptFile() if os.path.isfile(self.grading_script_filename): self.question_variants, \ self.answers_variants, \ self.answer_scores = get_testset_answers(self.grading_script_filename) self.processed_img, \ self.qr_code_id, \ self.variant, \ self.valid_answers = process_answer_sheet(self.img, self.template_filename) self.pixmap = cv_to_qpixmap( cv.cvtColor(self.processed_img, cv.COLOR_BGR2RGB)) if self.variant == 0: self.variant = SetVersionDialog.getManualVersion() if 0 < self.variant and len(self.question_variants) > 0 and \ len(self.answers_variants) > 0 and len(self.answer_scores) > 0: self.score = self.computeScore(self.variant, self.valid_answers, self.question_variants, self.answers_variants, self.answer_scores) self.updateWidgetsState.emit() self.updateImageView.emit() self.updateSheetResults.emit() @Slot() def saveResult(self): if self.score > -1.0: if self.student_records_filename is None or len( self.student_records) == 0: self.setStudentRecordsFile() if len(self.student_records) > 0: stud_id = SaveResultDialog.getRecordId(self.student_records) if 0 <= stud_id < len(self.student_records): self.student_records[stud_id][2] = "{:.0f}".format( self.score) if self.pdf_image_idx >= 0 and len( self.pdf_image_refs) > 0: self.student_records[stud_id][3] = "{:d}".format( self.pdf_image_idx) self.updateWidgetsState.emit() with open(self.student_records_filename, "w") as csvfile: records = csv.writer(csvfile, delimiter=",", quotechar='"') records.writerows(self.student_records) csvfile.close() @Slot() def onUpdateWidgetsState(self): self.ui.action_Open_PDF_collection.setEnabled( self.template_filename is not None) pdf_whats_this = "Open PDF collection of scanned answer sheets" if self.template_filename is not None \ else "Must set template file" self.ui.action_Open_PDF_collection.setWhatsThis(pdf_whats_this) self.ui.action_Open_image.setEnabled( self.template_filename is not None) self.ui.menu_Open_recent.setEnabled(self.template_filename is not None and len(self.recent_files) > 0) self.ui.menu_Open_recent.clear() self.recent_files_actions = [] self.recent_files = [f for f in self.recent_files if os.path.isfile(f)] for idx, filename in enumerate(self.recent_files[::-1]): action = QAction("&{:d} {:s}".format(idx + 1, os.path.basename(filename))) action.setToolTip(filename) action.setVisible(True) action.setData(filename) action.triggered.connect(self.openRecent) self.recent_files_actions.append(action) self.ui.menu_Open_recent.addAction(action) self.ui.nextButton.setEnabled( 0 <= self.pdf_image_idx < len(self.pdf_image_refs) - 1) self.ui.prevButton.setEnabled(self.pdf_image_idx > 0) self.ui.processButton.setEnabled(self.img is not None) self.ui.saveResultButton.setEnabled(len(self.valid_answers) > 0) self.ui.statusbar.clearMessage() status_message = "" if self.pdf_image_idx >= 0 and len(self.pdf_image_refs) > 0: score_message = ", no records file set" if self.student_records_filename is not None and len( self.student_records) > 0: score_message = ", not found in records" for record in self.student_records: try: stud_id = int(record[3]) stud_score = float(record[2]) stud_name = record[1] stud_group = record[0] if stud_id == self.pdf_image_idx: score_message = ", in records as '{:s}' from {:s} "\ "with a score of {:.0f}".format(stud_name, stud_group, stud_score) break except ValueError: pass status_message = "Answer sheet {:2d} of {:d}{:s}".format( self.pdf_image_idx + 1, len(self.pdf_image_refs), score_message) self.ui.statusbar.showMessage(status_message) @Slot() def onUpdateImageView(self): if self.pixmap is not None: self.ui.imageView.setPixmap( self.pixmap.scaled(self.ui.imageView.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)) @Slot() def onUpdateSheetResults(self): self.ui.idText.setText(self.qr_code_id) self.ui.versionText.setText( '{:d}'.format(self.variant) if self.variant > 0 else '') scoreText = "{:.0f}".format( self.score ) if self.score >= 0.0 else 'Not scored yet.' if self.variant > 0 else '' self.ui.scoreText.setText(scoreText) self.ui.answerList.clear() for q in sorted(self.valid_answers, key=lambda x: int(x)): self.ui.answerList.addItem( QListWidgetItem("{:3d} -> {:s}".format( int(q), self.valid_answers[q].upper())))
class JobManager(QAbstractListModel): """ Manager to handle active jobs and stdout, stderr and progress parsers. Also functions as a Qt data model for a view displaying progress for each process. """ _jobs = {} _state = {} _parsers = {} status = Signal(str) result = Signal(str, object) def __init__(self): super().__init__() self.status_timer = QTimer() self.status_timer.setInterval(100) self.status_timer.timeout.connect(self.notify_status) self.status_timer.start() def notify_status(self): n_jobs = len(self._jobs) self.status.emit("{} jobs".format(n_jobs)) def execute(self, command, parsers=no_parsers): """ Enqueue a worker to run (at some point) by passing it to the QThreadPool. """ job_id = uuid.uuid4().hex # By default, the signals do not have access to any information about # the process that sent it. So we use this constructor to annotate # each signal with a job_id. def fwd_signal(target): return lambda *args: target(job_id, *args) self._parsers[job_id] = parsers # Set default status to waiting, 0 progress. self._state[job_id] = DEFAULT_STATE.copy() p = QProcess() p.readyReadStandardOutput.connect(fwd_signal(self.handle_output)) p.readyReadStandardError.connect(fwd_signal(self.handle_output)) p.stateChanged.connect(fwd_signal(self.handle_state)) p.finished.connect(fwd_signal(self.done)) self._jobs[job_id] = p p.start(command) self.layoutChanged.emit() def handle_output(self, job_id): p = self._jobs[job_id] stderr = bytes(p.readAllStandardError()).decode("utf8") stdout = bytes(p.readAllStandardOutput()).decode("utf8") output = stderr + stdout parser = self._parsers.get(job_id) if parser.progress: progress = parser.progress(output) if progress: self._state[job_id]["progress"] = progress self.layoutChanged.emit() if parser.data: data = parser.data(output) if data: self.result.emit(job_id, data) def handle_state(self, job_id, state): self._state[job_id]["status"] = state self.layoutChanged.emit() def done(self, job_id, exit_code, exit_status): """ Task/worker complete. Remove it from the active workers dictionary. We leave it in worker_state, as this is used to to display past/complete workers too. """ del self._jobs[job_id] self.layoutChanged.emit() def cleanup(self): """ Remove any complete/failed workers from worker_state. """ for job_id, s in list(self._state.items()): if s["status"] == QProcess.NotRunning: del self._state[job_id] self.layoutChanged.emit() # Model interface def data(self, index, role): if role == Qt.DisplayRole: # See below for the data structure. job_ids = list(self._state.keys()) job_id = job_ids[index.row()] return job_id, self._state[job_id] def rowCount(self, index): return len(self._state)
class MietverhaeltnisView(QWidget, ModifyInfo): save = Signal() dataChanged = Signal() nextMv = Signal() prevMv = Signal() def __init__(self, mietverhaeltnis: XMietverhaeltnis = None, withSaveButton: bool = False, enableBrowsing=False, parent=None): QWidget.__init__(self, parent) ModifyInfo.__init__(self) self._mietverhaeltnis: XMietverhaeltnis = None self._withSaveButton = withSaveButton self._enableBrowsing = enableBrowsing # ob die Browse-Buttons angezeigt werden self._layout = QGridLayout() self._btnSave = QPushButton() self._btnVor = QPushButton() self._btnRueck = QPushButton() self._sdBeginnMietverh = SmartDateEdit() self._sdEndeMietverh = SmartDateEdit() self._edMieterName_1 = BaseEdit() self._edMieterVorname_1 = BaseEdit() self._edMieterName_2 = BaseEdit() self._edMieterVorname_2 = BaseEdit() self._edMieterTelefon = BaseEdit() self._edMieterMobil = BaseEdit() self._edMieterMailto = BaseEdit() self._edAnzPers = IntEdit() self._edNettomiete = FloatEdit() self._edNkv = FloatEdit() self._edKaution = IntEdit() self._sdKautionBezahltAm = SmartDateEdit() self._txtBemerkung1 = MultiLineEdit() self._txtBemerkung2 = MultiLineEdit() self._createGui() if mietverhaeltnis: self.setMietverhaeltnisData(mietverhaeltnis) #self.connectWidgetsToChangeSlot( self.onChange ) self.connectWidgetsToChangeSlot(self.onChange, self.onResetChangeFlag) def onChange(self, newcontent: str = None): if not self._btnSave.isEnabled(): self.setSaveButtonEnabled() self.dataChanged.emit() def onResetChangeFlag(self): self.setSaveButtonEnabled(False) def setSaveButtonEnabled(self, enabled: bool = True): self._btnSave.setEnabled(enabled) def _createGui(self): hbox = QHBoxLayout() if self._withSaveButton: self._createSaveButton(hbox) if self._enableBrowsing: self._createVorRueckButtons(hbox) self._layout.addLayout(hbox, 0, 0, alignment=Qt.AlignLeft) self._addHorizontalLine(1) self._createFelder(2) self.setLayout(self._layout) def _createSaveButton(self, hbox): btn = self._btnSave btn.clicked.connect(self.save.emit) btn.setFlat(True) btn.setEnabled(False) btn.setToolTip("Änderungen am Mietverhältnis speichern") icon = ImageFactory.inst().getSaveIcon() #icon = QIcon( "./images/save_30.png" ) btn.setIcon(icon) size = QSize(32, 32) btn.setFixedSize(size) iconsize = QSize(30, 30) btn.setIconSize(iconsize) hbox.addWidget(btn) def _createVorRueckButtons(self, hbox): self._prepareButton(self._btnRueck, ImageFactory.inst().getPrevIcon(), "Zum vorigen Mietverhältnis blättern", self.prevMv) hbox.addWidget(self._btnRueck) self._prepareButton(self._btnVor, ImageFactory.inst().getNextIcon(), "Zum nächsten Mietverhältnis blättern", self.nextMv) hbox.addWidget(self._btnVor) def _prepareButton(self, btn: QPushButton, icon: QIcon, tooltip: str, signal: Signal): btn.setFlat(True) btn.setEnabled(True) btn.setToolTip(tooltip) btn.setIcon(icon) size = QSize(32, 32) btn.setFixedSize(size) iconsize = QSize(30, 30) btn.setIconSize(iconsize) btn.clicked.connect(signal.emit) def _addHorizontalLine(self, r: int): hline = HLine() self._layout.addWidget(hline, r, 0, 1, 2) return r + 1 def _createFelder(self, r): c = 0 l = self._layout lbl = QLabel("Beginn: ") l.addWidget(lbl, r, c) c += 1 self._sdBeginnMietverh.setMaximumWidth(100) l.addWidget(self._sdBeginnMietverh, r, c) c = 0 r += 1 l.addWidget(BaseLabel("Ende: "), r, c) c += 1 self._sdEndeMietverh.setMaximumWidth(100) l.addWidget(self._sdEndeMietverh, r, c) c = 0 r += 1 lbl = BaseLabel("Name / Vorname 1. Mieter: ") l.addWidget(lbl, r, c) c += 1 hbox = QHBoxLayout() hbox.addWidget(self._edMieterName_1) hbox.addWidget(self._edMieterVorname_1) l.addLayout(hbox, r, c) c = 0 r += 1 lbl = BaseLabel("Name / Vorname 2. Mieter: ") l.addWidget(lbl, r, c) c += 1 hbox = QHBoxLayout() hbox.addWidget(self._edMieterName_2) hbox.addWidget(self._edMieterVorname_2) l.addLayout(hbox, r, c) c = 0 r += 1 l.addWidget(BaseLabel("Telefon: "), r, c) c += 1 l.addWidget(self._edMieterTelefon, r, c) c = 0 r += 1 l.addWidget(BaseLabel("Mobil: "), r, c) c += 1 l.addWidget(self._edMieterMobil, r, c) c = 0 r += 1 l.addWidget(BaseLabel("Mailadresse: "), r, c) c += 1 l.addWidget(self._edMieterMailto, r, c) c = 0 r += 1 l.addWidget(BaseLabel("Anzahl Personen i.d. Whg: "), r, c) c += 1 self._edAnzPers.setMaximumWidth(20) l.addWidget(self._edAnzPers, r, c) c = 0 r += 1 l.addWidget(BaseLabel("Nettomiete / NKV: "), r, c) c += 1 self._edNettomiete.setMaximumWidth(100) #self._edNettomiete.setEnabled( False ) self._edNkv.setMaximumWidth(100) #self._edNkv.setEnabled( False ) hbox = QHBoxLayout() hbox.addWidget(self._edNettomiete) hbox.addWidget(self._edNkv) hbox.addWidget( BaseLabel( " Änderungen der Miete und NKV über Dialog 'Sollmiete'")) l.addLayout(hbox, r, c, alignment=Qt.AlignLeft) c = 0 r += 1 l.addWidget(BaseLabel("Kaution: "), r, c) c += 1 self._edKaution.setMaximumWidth(100) l.addWidget(self._edKaution, r, c) c = 0 r += 1 l.addWidget(BaseLabel("Kaution bezahlt am: "), r, c) c += 1 self._sdKautionBezahltAm.setMaximumWidth(100) l.addWidget(self._sdKautionBezahltAm, r, c) c = 0 r += 1 l.addWidget(BaseLabel(""), r, c) r += 1 l.addWidget(BaseLabel("Bemerkungen: "), r, c) c += 1 hbox = QHBoxLayout() hbox.addWidget(self._txtBemerkung1) hbox.addWidget(self._txtBemerkung2) l.addLayout(hbox, r, c) # cols = self._layout.columnCount() # print( cols, " columns" ) def setNettoUndNkvEnabled(self, enabled: bool = True): self._edNettomiete.setEnabled(enabled) self._edNkv.setEnabled(enabled) def _guiToData(self, x: XMietverhaeltnis): """ Überträgt die Änderungen, die der User im GUI gemacht hat, in das übergebene XMietverhaeltnis-Objekt :param x: XMietverhaeltnis-Objekt, in das die geänderten Daten übertragen werden :return: """ x.von = self._sdBeginnMietverh.getDate() x.bis = self._sdEndeMietverh.getDate() x.name = self._edMieterName_1.text() x.vorname = self._edMieterVorname_1.text() x.name2 = self._edMieterName_2.text() x.vorname2 = self._edMieterVorname_2.text() x.telefon = self._edMieterTelefon.text() x.mobil = self._edMieterMobil.text() x.mailto = self._edMieterMailto.text() x.anzahl_pers = self._edAnzPers.getIntValue() x.nettomiete = self._edNettomiete.getFloatValue() x.nkv = self._edNkv.getFloatValue() x.kaution = self._edKaution.getIntValue() x.kaution_bezahlt_am = self._sdKautionBezahltAm.getDate() x.bemerkung1 = self._txtBemerkung1.toPlainText() x.bemerkung2 = self._txtBemerkung2.toPlainText() def getMietverhaeltnisCopyWithChanges(self) -> XMietverhaeltnis: """ gibt eine Kopie der Mietverhaeltnis-Schnittstellendaten mit Änderungen zurück. Diese Kopie kann für Validierungszwecke verwendet werden. :return: Kopie von XMietverhaeltnis """ mvcopy = copy.copy(self._mietverhaeltnis) self._guiToData(mvcopy) return mvcopy def applyChanges(self): """ überträgt die Änderungen, die der User im GUI gemacht hat, in das originale XMietverhaeltnis-Objekt. """ if self.isChanged(): self._guiToData(self._mietverhaeltnis) def setMietverhaeltnisData(self, mv: XMietverhaeltnis): """ Daten, die im GUI angezeigt und geändert werden können. :param mv: :return: """ self._mietverhaeltnis = mv if mv.von: self._sdBeginnMietverh.setDateFromIsoString(mv.von) if mv.bis: self._sdEndeMietverh.setDateFromIsoString(mv.bis) self._edMieterName_1.setText(mv.name) self._edMieterVorname_1.setText(mv.vorname) self._edMieterName_2.setText(mv.name2) self._edMieterVorname_2.setText(mv.vorname2) self._edMieterTelefon.setText(mv.telefon) self._edMieterMobil.setText(mv.mobil) self._edMieterMailto.setText(mv.mailto) self._edAnzPers.setIntValue(mv.anzahl_pers) self._edNettomiete.setFloatValue(mv.nettomiete) self._edNkv.setFloatValue(mv.nkv) if mv.kaution: self._edKaution.setIntValue(mv.kaution) if mv.kaution_bezahlt_am: self._sdKautionBezahltAm.setDateFromIsoString( mv.kaution_bezahlt_am) self._txtBemerkung1.setText(mv.bemerkung1) self._txtBemerkung2.setText(mv.bemerkung2) self.resetChangeFlag() def clear(self): self._sdBeginnMietverh.clear() self._sdEndeMietverh.clear() self._edMieterName_1.clear() self._edMieterVorname_1.clear() self._edMieterName_2.clear() self._edMieterVorname_2.clear() self._edMieterTelefon.clear() self._edMieterMobil.clear() self._edMieterMailto.clear() self._edAnzPers.clear() self._edNettomiete.clear() self._edNkv.clear() self._edKaution.clear() self._sdKautionBezahltAm.clear() self._txtBemerkung1.clear() self._txtBemerkung2.clear()
class KeyEventFilter(QObject): """ event filter for the brush context filter key events. the following singnals are emitted: meta_pressed / meta_released ctrl_pressed / ctrl_released ctrl_shift_pressed / shift_released""" # modifier meta_pressed = Signal() # ctrl on MacOs, windows ond win meta_released = Signal() ctrl_pressed = Signal() ctrl_released = Signal() shift_pressed = Signal() shift_released = Signal() ctrl_shift_pressed = Signal() ctrl_shift_released = Signal() meta_shift_preffed = Signal() mata_shift_released = Signal() # key event signals space_pressed = Signal() space_pressed = Signal() b_pressed = Signal() b_released = Signal() def __init__(self, parent): super(KeyEventFilter, self).__init__() self.parent = parent def eventFilter(self, source, event): if isinstance(event, QKeyEvent) and not event.isAutoRepeat(): if event.type() == QEvent.KeyPress: if event.key() == Qt.Key_Control: self.ctrl_pressed.emit() return True if event.key() == Qt.Key_Shift: self.shift_pressed.emit() return True if event.key() == Qt.Key_Meta: self.meta_pressed.emit() return True if event.key() == Qt.Key_B: self.b_pressed.emit() return True if event.type() == QEvent.KeyRelease: if event.key() == Qt.Key_Control: self.ctrl_released.emit() return True if event.key() == Qt.Key_Shift: self.shift_released.emit() return True if event.key() == Qt.Key_Meta: self.meta_released.emit() return True if event.key() == Qt.Key_B: self.b_released.emit() return True return False
class FunctionDefTreeModel(QAbstractItemModel): functionItemsChanged = Signal(int) def __init__(self, parent: Optional[QObject] = ...): super().__init__(parent) self.rootItem = FunctionDefItem(title='Root', item_type=FunctionDefItemType.Module) self.loadThread = None self.allFuncItemsCached = None signalHub.filterFuncDefTree.connect(self.loadFunctionItems) signalHub.dataFileOpened.connect(self.loadNewDataFile) signalHub.exitingApp.connect(self.exitApp) def rowCount(self, parent: QModelIndex = ...) -> int: if not parent.isValid(): parent_item = self.rootItem else: parent_item = parent.internalPointer() return parent_item.rowCount() def columnCount(self, parent: QModelIndex = ...) -> int: return 1 def index(self, row: int, column: int, parent: QModelIndex = ...) -> QModelIndex: if not self.hasIndex(row, column, parent): return QModelIndex() if not parent.isValid(): parent_item = self.rootItem else: parent_item = parent.internalPointer() child_item = parent_item.child(row) if child_item: return self.createIndex(row, column, child_item) else: return QModelIndex() def data(self, index: QModelIndex, role: int = ...) -> Any: if not index.isValid(): return None if role != Qt.DisplayRole: return None item = index.internalPointer() return item.title def parent(self, index: QModelIndex) -> QModelIndex: if not index.isValid(): return QModelIndex() child_item = index.internalPointer() parent_item = child_item.parent if parent_item == self.rootItem: return QModelIndex() return self.createIndex(parent_item.row(), 0, parent_item) @Slot() def loadNewDataFile(self): self.beginResetModel() self.rootItem = FunctionDefItem(title='Root', item_type=FunctionDefItemType.Module) self.endResetModel() self.allFuncItemsCached = None self.loadFunctionItems('') @Slot(str) def loadFunctionItems(self, func_name): if self.loadThread: self.loadThread.resultReady.disconnect() self.loadThread.stop() signalHub.showStatusBarMessage.emit('Loading...') self.loadThread = FunctionDefTreeModelThread(func_name) self.loadThread.resultReady.connect(self.loadFunctionItemsDone) if (not func_name) and self.allFuncItemsCached: self.loadFunctionItemsDone(self.allFuncItemsCached) else: self.loadThread.start() @Slot(object) def loadFunctionItemsDone(self, result): root_item, func_name, func_count = result if root_item: self.beginResetModel() self.rootItem = root_item self.endResetModel() if not func_name: self.allFuncItemsCached = result self.functionItemsChanged.emit(func_count) signalHub.showStatusBarMessage.emit('Ready') @Slot() def exitApp(self): if self.loadThread: self.loadThread.resultReady.disconnect() self.loadThread.stop() self.loadThread = None
class MouseEventFilter(QObject): """ event filter for the brush context filter mouse and key events to trigger the user interaction """ # mouse event signals mouse_moved = Signal(QPoint) clicked = Signal(QPoint) dragged = Signal(QPoint) released = Signal(QPoint) leave = Signal() def __init__(self, parent): super(MouseEventFilter, self).__init__() self.parent = parent self.is_clicked = False def eventFilter(self, source, event): modifier = QGuiApplication.queryKeyboardModifiers() if event.type() == QEvent.MouseMove: # emit mouse moved signa and the drag signal if the mouse # button is clicked position = event.pos() self.mouse_moved.emit(position) if self.is_clicked: self.dragged.emit(position) return False if event.type() == QEvent.Wheel: # emit the mouse moved signal when the mousewheel is used position = event.pos() self.mouse_moved.emit(position) if event.type() == QEvent.MouseButtonPress: # set the mouse button clicked state and emit the clicked signal # if neither control nor alt modifiers are pressed if not modifier == Qt.ControlModifier\ and not modifier == Qt.AltModifier: self.is_clicked = True position = event.pos() self.clicked.emit(position) return False if event.type() == QEvent.MouseButtonRelease: # release the mouse button clicked state and emit the release # signal if neither ctontrol nor alt modifiers are pressed. self.is_clicked = False if not modifier == Qt.ControlModifier\ and not modifier == Qt.AltModifier: position = event.pos() self.released.emit(position) return False if event.type() == QEvent.Leave: # emit a leave signal self.leave.emit() return False return False
class StateLogic(QObject): """ """ projectCreatedChanged = Signal() simulationParametersChanged = Signal() undoRedoChanged = Signal() parametersChanged = Signal() experimentLoadedChanged = Signal() experimentSkippedChanged = Signal() phasesEnabled = Signal() phasesAsObjChanged = Signal() structureParametersChanged = Signal() projectInfoChanged = Signal() experimentDataAdded = Signal() removePhaseSignal = Signal(str) resetUndoRedoStack = Signal() currentMinimizerIndex = Signal(int) currentMinimizerMethodIndex = Signal(int) def __init__(self, parent=None, interface=None): super().__init__(parent) self.parent = parent self._interface = interface self._interface_name = interface.current_interface_name self.project_save_filepath = "" self.project_load_filepath = "" self._project_info = self._defaultProjectInfo() self._project_created = False self._experiment_parameters = None self._experiment_data_as_xml = "" self.experiment_data = None self._experiment_data = None self._experiment_loaded = False self._experiment_skipped = False self.experiments = self._defaultExperiments() self._parameters = None self._instrument_parameters = None self._status_model = None self._state_changed = False self._report = "" self.phases = None self._phases_as_obj = [] self._phases_as_xml = "" self._phases_as_cif = "" self._sample = self._defaultSample() self._current_phase_index = 0 # Experiment self._pattern_parameters_as_obj = self._defaultPatternParameters() self._instrument_parameters_as_obj = self._defaultInstrumentParameters( ) # noqa: E501 self._instrument_parameters_as_xml = "" # Parameters self._parameters_as_obj = [] self._parameters_as_xml = [] self._parameters_filter_criteria = "" self._data = self._defaultData() self._simulation_parameters_as_obj = self._defaultSimulationParameters( ) # noqa: E501 self._currentProjectPath = os.path.expanduser("~") #################################################################################################################### #################################################################################################################### # data #################################################################################################################### #################################################################################################################### def _defaultData(self): x_min = self._defaultSimulationParameters()['x_min'] x_max = self._defaultSimulationParameters()['x_max'] x_step = self._defaultSimulationParameters()['x_step'] num_points = int((x_max - x_min) / x_step + 1) x_data = np.linspace(x_min, x_max, num_points) data = DataStore() data.append( DataSet1D(name='PND', x=x_data, y=np.zeros_like(x_data), x_label='2theta (deg)', y_label='Intensity', data_type='experiment')) data.append( DataSet1D(name='{:s} engine'.format(self._interface_name), x=x_data, y=np.zeros_like(x_data), x_label='2theta (deg)', y_label='Intensity', data_type='simulation')) data.append( DataSet1D(name='Difference', x=x_data, y=np.zeros_like(x_data), x_label='2theta (deg)', y_label='Difference', data_type='simulation')) return data def _defaultSimulationParameters(self): return {"x_min": 10.0, "x_max": 120.0, "x_step": 0.1} #################################################################################################################### #################################################################################################################### # experiment #################################################################################################################### #################################################################################################################### def _defaultExperiment(self): return {"label": "D1A@ILL", "color": "#00a3e3"} def _loadExperimentData(self, file_url): print("+ _loadExperimentData") file_path = generalizePath(file_url) data = self._data.experiments[0] data.x, data.y, data.e = np.loadtxt(file_path, unpack=True) return data def _experimentDataParameters(self, data): x_min = data.x[0] x_max = data.x[-1] x_step = (x_max - x_min) / (len(data.x) - 1) parameters = {"x_min": x_min, "x_max": x_max, "x_step": x_step} return parameters def _onExperimentDataAdded(self): self._experiment_parameters = self._experimentDataParameters( self._experiment_data) # noqa: E501 self.simulationParametersAsObj(json.dumps( self._experiment_parameters)) # noqa: E501 self.experiments = [self._defaultExperiment()] def experimentDataXYZ(self): return (self._experiment_data.x, self._experiment_data.y, self._experiment_data.e) # noqa: E501 def _defaultExperiments(self): return [] def experimentLoaded(self, loaded: bool): if self._experiment_loaded == loaded: return self._experiment_loaded = loaded self.experimentLoadedChanged.emit() def experimentSkipped(self, skipped: bool): if self._experiment_skipped == skipped: return self._experiment_skipped = skipped self.experimentSkippedChanged.emit() def _setExperimentDataAsXml(self): self._experiment_data_as_xml = dicttoxml( self.experiments, attr_type=True).decode() # noqa: E501 def addExperimentDataFromXye(self, file_url): self._experiment_data = self._loadExperimentData(file_url) self._data.experiments[0].name = pathlib.Path(file_url).stem self.experiments = [{ 'name': experiment.name } for experiment in self._data.experiments] self.experimentLoaded(True) self.experimentSkipped(False) def removeExperiment(self): self.experiments.clear() self.experimentLoaded(False) self.experimentSkipped(False) #################################################################################################################### #################################################################################################################### # project #################################################################################################################### #################################################################################################################### def _defaultProjectInfo(self): return dict( name="Example Project", short_description="diffraction, powder, 1D", samples="Not loaded", experiments="Not loaded", modified=datetime.datetime.now().strftime("%d.%m.%Y %H:%M")) def projectExamplesAsXml(self): model = [{ "name": "PbSO4", "description": "neutrons, powder, 1D, D1A@ILL", "path": "../Resources/Examples/PbSO4/project.json" }, { "name": "Co2SiO4", "description": "neutrons, powder, 1D, D20@ILL", "path": "../Resources/Examples/Co2SiO4/project.json" }, { "name": "Dy3Al5O12", "description": "neutrons, powder, 1D, G41@LLB", "path": "../Resources/Examples/Dy3Al5O12/project.json" }] xml = dicttoxml(model, attr_type=False) xml = xml.decode() return xml def projectInfoAsCif(self): cif_list = [] for key, value in self._project_info.items(): if ' ' in value: value = f"'{value}'" cif_list.append(f'_{key} {value}') cif_str = '\n'.join(cif_list) return cif_str def projectInfoAsJson(self, json_str): self._project_info = json.loads(json_str) def editProjectInfo(self, key, value): if key == 'location': self._currentProjectPath = value return else: if self._project_info[key] == value: return self._project_info[key] = value def currentProjectPath(self, new_path): if self._currentProjectPath == new_path: return self._currentProjectPath = new_path def createProject(self): projectPath = self._currentProjectPath mainCif = os.path.join(projectPath, 'project.cif') samplesPath = os.path.join(projectPath, 'samples') experimentsPath = os.path.join(projectPath, 'experiments') calculationsPath = os.path.join(projectPath, 'calculations') if not os.path.exists(projectPath): os.makedirs(projectPath) os.makedirs(samplesPath) os.makedirs(experimentsPath) os.makedirs(calculationsPath) with open(mainCif, 'w') as file: file.write(self.projectInfoAsCif()) else: print(f"ERROR: Directory {projectPath} already exists") def stateHasChanged(self, changed: bool): if self._state_changed == changed: return self._state_changed = changed def _loadProjectAs(self, filepath): """ """ self.project_load_filepath = filepath print("LoadProjectAs " + filepath) self._loadProject() def _loadProject(self): """ """ path = generalizePath(self.project_load_filepath) if not os.path.isfile(path): print("Failed to find project: '{0}'".format(path)) return with open(path, 'r') as xml_file: descr: dict = json.load(xml_file) interface_name = descr.get('interface', None) if interface_name is not None: old_interface_name = self._interface.current_interface_name if old_interface_name != interface_name: self._interface.switch(interface_name) self._sample = Sample.from_dict(descr['sample']) self._sample.interface = self._interface # send signal to tell the proxy we changed phases self.phasesEnabled.emit() self.phasesAsObjChanged.emit() self.structureParametersChanged.emit() # experiment if 'experiments' in descr: self.experimentLoaded(True) self.experimentSkipped(False) self._data.experiments[0].x = np.array(descr['experiments'][0]) self._data.experiments[0].y = np.array(descr['experiments'][1]) self._data.experiments[0].e = np.array(descr['experiments'][2]) self._experiment_data = self._data.experiments[0] self.experiments = [{'name': descr['project_info']['experiments']}] self.setCurrentExperimentDatasetName( descr['project_info']['experiments']) # send signal to tell the proxy we changed experiment self.experimentDataAdded.emit() self.parametersChanged.emit() self.experimentLoadedChanged.emit() else: # delete existing experiment self.removeExperiment() self.experimentLoaded(False) if descr['experiment_skipped']: self.experimentSkipped(True) self.experimentSkippedChanged.emit() # project info self._project_info = descr['project_info'] new_minimizer_settings = descr.get('minimizer', None) if new_minimizer_settings is not None: new_engine = new_minimizer_settings['engine'] new_method = new_minimizer_settings['method'] new_engine_index = self.parent.minimizerNames().index(new_engine) self.currentMinimizerIndex.emit(new_engine_index) new_method_index = self.parent.minimizerMethodNames().index( new_method) self.currentMinimizerMethodIndex.emit(new_method_index) self.parent.fitLogic.fitter.fit_object = self._sample self.resetUndoRedoStack.emit() self.setProjectCreated(True) def experimentDataAsObj(self): return [{ 'name': experiment.name } for experiment in self._data.experiments] def saveProject(self): """ """ projectPath = self._currentProjectPath project_save_filepath = os.path.join(projectPath, 'project.json') descr = {'sample': self._sample.as_dict(skip=['interface'])} if self._data.experiments: experiments_x = self._data.experiments[0].x experiments_y = self._data.experiments[0].y experiments_e = self._data.experiments[0].e descr['experiments'] = [ experiments_x, experiments_y, experiments_e ] descr['experiment_skipped'] = self._experiment_skipped descr['project_info'] = self._project_info descr['interface'] = self._interface.current_interface_name descr['minimizer'] = { 'engine': self.parent.fitLogic.fitter.current_engine.name, 'method': self.parent.currentMinimizerMethodName() } content_json = json.dumps(descr, indent=4, default=self.default) path = generalizePath(project_save_filepath) createFile(path, content_json) def default(self, obj): if type(obj).__module__ == np.__name__: if isinstance(obj, np.ndarray): return obj.tolist() else: return obj.item() raise TypeError('Unknown type:', type(obj)) def resetState(self): self._project_info = self._defaultProjectInfo() self.setProjectCreated(False) self.projectInfoChanged.emit() self.project_save_filepath = "" self.removeExperiment() self.removePhaseSignal.emit( self._sample.phases[self._current_phase_index].name) #################################################################################################################### #################################################################################################################### # SAMPLE #################################################################################################################### #################################################################################################################### def _defaultSample(self): sample = Sample(parameters=Pars1D.default(), pattern=Pattern1D.default(), interface=self._interface) sample.pattern.zero_shift = 0.0 sample.pattern.scale = 100.0 sample.parameters.wavelength = 1.912 sample.parameters.resolution_u = 0.1447 sample.parameters.resolution_v = -0.4252 sample.parameters.resolution_w = 0.3864 sample.parameters.resolution_x = 0.0 sample.parameters.resolution_y = 0.0 # 0.0961 return sample def addSampleFromCif(self, cif_url): cif_path = generalizePath(cif_url) borg.stack.enabled = False self._sample.phases = Phases.from_cif_file(cif_path) borg.stack.enabled = True def setCurrentPhaseName(self, name): if self._sample.phases[self._current_phase_index].name == name: return self._sample.phases[self._current_phase_index].name = name self._project_info['samples'] = name #################################################################################################################### #################################################################################################################### # phases #################################################################################################################### #################################################################################################################### def currentPhaseIndex(self, new_index: int): if self._current_phase_index == new_index or new_index == -1: return False self._current_phase_index = new_index return True def removePhase(self, phase_name: str): if phase_name in self._sample.phases.phase_names: del self._sample.phases[phase_name] return True return False def addDefaultPhase(self): borg.stack.enabled = False self._sample.phases.append(self._defaultPhase()) borg.stack.enabled = True def _defaultPhase(self): space_group = SpaceGroup.from_pars('P 42/n c m') cell = Lattice.from_pars(8.56, 8.56, 6.12, 90, 90, 90) atom = Site.from_pars(label='Cl1', specie='Cl', fract_x=0.125, fract_y=0.167, fract_z=0.107) # noqa: E501 atom.add_adp('Uiso', Uiso=0.0) phase = Phase('Dichlorine', spacegroup=space_group, cell=cell) phase.add_atom(atom) return phase def _onPhaseAdded(self, background_obj): if self._interface.current_interface_name != 'CrysPy': self._interface.generate_sample_binding("filename", self._sample) self._sample.phases.name = 'Phases' self._project_info['samples'] = self._sample.phases[ self._current_phase_index].name # noqa: E501 # self._sample.set_background(background_obj) def currentCrystalSystem(self): phases = self._sample.phases if not phases: return '' current_system = phases[ self._current_phase_index].spacegroup.crystal_system # noqa: E501 current_system = current_system.capitalize() return current_system def setCurrentCrystalSystem(self, new_system: str): new_system = new_system.lower() space_group_numbers = SpacegroupInfo.get_ints_from_system(new_system) top_space_group_number = space_group_numbers[0] top_space_group_name = SpacegroupInfo.get_symbol_from_int_number( top_space_group_number) # noqa: E501 self._setCurrentSpaceGroup(top_space_group_name) def phasesAsExtendedCif(self): if len(self._sample.phases) == 0: return symm_ops = self._sample.phases[0].spacegroup.symmetry_opts symm_ops_cif_loop = "loop_\n _symmetry_equiv_pos_as_xyz\n" for symm_op in symm_ops: symm_ops_cif_loop += f' {symm_op.as_xyz_string()}\n' return self._phases_as_cif + symm_ops_cif_loop def phasesAsCif(self, phases_as_cif): if self._phases_as_cif == phases_as_cif: return self._sample.phases = Phases.from_cif_str(phases_as_cif) def _setPhasesAsObj(self): self._phases_as_obj = self._sample.phases.as_dict( skip=['interface'])['data'] def _setPhasesAsXml(self): self._phases_as_xml = dicttoxml(self._phases_as_obj, attr_type=True).decode() # noqa: E501 def _setPhasesAsCif(self): self._phases_as_cif = str(self._sample.phases.cif) def _setCurrentSpaceGroup(self, new_name: str): phases = self._sample.phases if phases[ self. _current_phase_index].spacegroup.space_group_HM_name == new_name: # noqa: E501 return phases[ self. _current_phase_index].spacegroup.space_group_HM_name = new_name # noqa: E501 def _spaceGroupSettingList(self): phases = self._sample.phases if not phases: return [] current_number = self._currentSpaceGroupNumber() settings = SpacegroupInfo.get_compatible_HM_from_int(current_number) return settings def _spaceGroupNumbers(self): current_system = self.currentCrystalSystem().lower() numbers = SpacegroupInfo.get_ints_from_system(current_system) return numbers def _currentSpaceGroupNumber(self): phases = self._sample.phases current_number = phases[ self._current_phase_index].spacegroup.int_number # noqa: E501 return current_number def getCurrentSpaceGroup(self): def space_group_index(number, numbers): if number in numbers: return numbers.index(number) return 0 phases = self._sample.phases if not phases: return -1 space_group_numbers = self._spaceGroupNumbers() current_number = self._currentSpaceGroupNumber() current_idx = space_group_index(current_number, space_group_numbers) return current_idx def currentSpaceGroup(self, new_idx: int): space_group_numbers = self._spaceGroupNumbers() space_group_number = space_group_numbers[new_idx] space_group_name = SpacegroupInfo.get_symbol_from_int_number( space_group_number) # noqa: E501 self._setCurrentSpaceGroup(space_group_name) def formattedSpaceGroupList(self): def format_display(num): name = SpacegroupInfo.get_symbol_from_int_number(num) return f"<font color='#999'>{num}</font> {name}" space_group_numbers = self._spaceGroupNumbers() display_list = [format_display(num) for num in space_group_numbers] return display_list def crystalSystemList(self): systems = [ system.capitalize() for system in SpacegroupInfo.get_all_systems() ] # noqa: E501 return systems def formattedSpaceGroupSettingList(self): def format_display(num, name): return f"<font color='#999'>{num}</font> {name}" raw_list = self._spaceGroupSettingList() formatted_list = [ format_display(i + 1, name) for i, name in enumerate(raw_list) ] # noqa: E501 return formatted_list def currentSpaceGroupSetting(self): phases = self._sample.phases if not phases: return 0 settings = self._spaceGroupSettingList() current_setting = phases[ self. _current_phase_index].spacegroup.space_group_HM_name.raw_value # noqa: E501 current_number = settings.index(current_setting) return current_number def setCurrentSpaceGroupSetting(self, new_number: int): settings = self._spaceGroupSettingList() name = settings[new_number] self._setCurrentSpaceGroup(name) #################################################################################################################### # Phase: Atoms #################################################################################################################### def addDefaultAtom(self): index = len(self._sample.phases[0].atoms.atom_labels) + 1 label = f'Label{index}' atom = Site.from_pars(label=label, specie='O', fract_x=0.05, fract_y=0.05, fract_z=0.05) atom.add_adp('Uiso', Uiso=0.0) self._sample.phases[self._current_phase_index].add_atom(atom) def removeAtom(self, atom_label: str): del self._sample.phases[self._current_phase_index].atoms[atom_label] def setCurrentExperimentDatasetName(self, name): if self._data.experiments[0].name == name: return self._data.experiments[0].name = name self._project_info['experiments'] = name #################################################################################################################### # Simulation parameters #################################################################################################################### def simulationParametersAsObj(self, json_str): if self._simulation_parameters_as_obj == json.loads(json_str): return self._simulation_parameters_as_obj = json.loads(json_str) self.simulationParametersChanged.emit() def _defaultPatternParameters(self): return {"scale": 1.0, "zero_shift": 0.0} def _setPatternParametersAsObj(self): parameters = self._sample.pattern.as_dict(skip=['interface']) self._pattern_parameters_as_obj = parameters #################################################################################################################### # Instrument parameters (wavelength, resolution_u, ..., resolution_y) #################################################################################################################### def _defaultInstrumentParameters(self): return { "wavelength": 1.0, "resolution_u": 0.01, "resolution_v": -0.01, "resolution_w": 0.01, "resolution_x": 0.0, "resolution_y": 0.0 } def _setInstrumentParametersAsObj(self): # parameters = self._sample.parameters.as_dict() parameters = self._sample.parameters.as_dict(skip=['interface']) self._instrument_parameters_as_obj = parameters def _setInstrumentParametersAsXml(self): parameters = [self._instrument_parameters_as_obj] self._instrument_parameters_as_xml = dicttoxml( parameters, attr_type=True).decode() # noqa: E501 #################################################################################################################### # Calculated data #################################################################################################################### def _updateCalculatedData(self): if not self._experiment_loaded and not self._experiment_skipped: return self._sample.output_index = self._current_phase_index # THIS IS WHERE WE WOULD LOOK UP CURRENT EXP INDEX sim = self._data.simulations[0] if self._experiment_loaded: exp = self._data.experiments[0] sim.x = exp.x elif self._experiment_skipped: x_min = float(self._simulation_parameters_as_obj['x_min']) x_max = float(self._simulation_parameters_as_obj['x_max']) x_step = float(self._simulation_parameters_as_obj['x_step']) num_points = int((x_max - x_min) / x_step + 1) sim.x = np.linspace(x_min, x_max, num_points) sim.y = self._interface.fit_func(sim.x) hkl = self._interface.get_hkl() self.parent.chartsLogic._plotting_1d_proxy.setCalculatedData( sim.x, sim.y) # noqa: E501 self.parent.chartsLogic._plotting_1d_proxy.setBraggData( hkl['ttheta'], hkl['h'], hkl['k'], hkl['l']) # noqa: E501 #################################################################################################################### # Fitables (parameters table from analysis tab & ...) #################################################################################################################### def _setParametersAsObj(self): self._parameters_as_obj.clear() par_ids, par_paths = generatePath(self._sample, True) for par_index, par_path in enumerate(par_paths): par_id = par_ids[par_index] par = borg.map.get_item_by_key(par_id) if not par.enabled: continue # add experimental dataset name par_path = par_path.replace( 'Pars1D.', f'Instrument.{self.experimentDataAsObj()[0]["name"]}.') par_path = par_path.replace( 'Pattern1D.', f'Instrument.{self.experimentDataAsObj()[0]["name"]}.') # par_path = par_path.replace('Instrument.', f'Instrument.{self.experimentDataAsObj()[0]["name"]}.') if self._parameters_filter_criteria.lower() not in par_path.lower( ): # noqa: E501 continue self._parameters_as_obj.append({ "id": str(par_id), "number": par_index + 1, "label": par_path, "value": par.raw_value, "unit": '{:~P}'.format(par.unit), "error": float(par.error), "fit": int(not par.fixed) }) def _setParametersAsXml(self): self._parameters_as_xml = dicttoxml( self._parameters_as_obj, attr_type=False).decode() # noqa: E501 def setParametersFilterCriteria(self, new_criteria): if self._parameters_filter_criteria == new_criteria: return self._parameters_filter_criteria = new_criteria #################################################################################################################### # Any parameter #################################################################################################################### def editParameter(self, obj_id: str, new_value: Union[bool, float, str]): # noqa: E501 if not obj_id: return obj = self._parameterObj(obj_id) if obj is None: return if isinstance(new_value, bool): if obj.fixed == (not new_value): return obj.fixed = not new_value self.parametersChanged.emit() self.undoRedoChanged.emit() else: if obj.raw_value == new_value: return obj.value = new_value self.parent.parametersChanged.emit() def _parameterObj(self, obj_id: str): if not obj_id: return obj_id = int(obj_id) obj = borg.map.get_item_by_key(obj_id) return obj #################################################################################################################### #################################################################################################################### # STATUS #################################################################################################################### #################################################################################################################### def statusModelAsObj(self, current_engine, current_minimizer): obj = { "calculation": self._interface.current_interface_name, "minimization": f'{current_engine} ({current_minimizer})' # noqa: E501 } self._status_model = obj return obj def statusModelAsXml(self, current_engine, current_minimizer): model = [ { "label": "Calculation", "value": self._interface.current_interface_name }, # noqa: E501 { "label": "Minimization", "value": f'{current_engine} ({current_minimizer})' } # noqa: E501 ] xml = dicttoxml(model, attr_type=False) xml = xml.decode() return xml #################################################################################################################### #################################################################################################################### # Reporting #################################################################################################################### #################################################################################################################### def setReport(self, report): """ Keep the QML generated HTML report for saving """ self._report = report def saveReport(self, filepath): """ Save the generated report to the specified file Currently only html """ try: with open(filepath, 'w', encoding='utf-8') as f: f.write(self._report) success = True except IOError: success = False return success def setProjectCreated(self, created: bool): if self._project_created == created: return self._project_created = created self.projectCreatedChanged.emit() #################################################################################################################### # Calculator #################################################################################################################### def _onCurrentCalculatorChanged(self): data = self._data.simulations data = data[0] data.name = f'{self._interface.current_interface_name} engine'
class SpineToolboxProject(MetaObject): """Class for Spine Toolbox projects.""" dag_execution_finished = Signal() project_execution_about_to_start = Signal() """Emitted just before the entire project is executed.""" project_execution_finished = Signal() """Emitted when the execution finishes.""" def __init__(self, toolbox, name, description, p_dir, project_item_model, settings, logger): """ Args: toolbox (ToolboxUI): toolbox of this project name (str): Project name description (str): Project description p_dir (str): Project directory project_item_model (ProjectItemModel): project item tree model settings (QSettings): Toolbox settings logger (LoggerInterface): a logger instance """ super().__init__(name, description) self._toolbox = toolbox self._project_item_model = project_item_model self._connections = list() self._logger = logger self._settings = settings self._dags_about_to_be_notified = set() self.dag_handler = DirectedGraphHandler() self._engine_workers = [] self._execution_stopped = True self.project_dir = None # Full path to project directory self.config_dir = None # Full path to .spinetoolbox directory self.items_dir = None # Full path to items directory self.specs_dir = None # Full path to specs directory self.config_file = None # Full path to .spinetoolbox/project.json file self._toolbox.undo_stack.clear() p_dir = os.path.abspath(p_dir) if not self._create_project_structure(p_dir): self._logger.msg_error.emit( "Creating project directory structure in <b>{0}</b> failed". format(p_dir)) def toolbox(self): """Called by ProjectItem to use the toolbox as logger for 'box' messages.""" return self._toolbox def connect_signals(self): """Connect signals to slots.""" self.dag_handler.dag_simulation_requested.connect( self.notify_changes_in_dag) def _create_project_structure(self, directory): """Makes the given directory a Spine Toolbox project directory. Creates directories and files that are common to all projects. Args: directory (str): Abs. path to a directory that should be made into a project directory Returns: bool: True if project structure was created successfully, False otherwise """ self.project_dir = directory self.config_dir = os.path.abspath( os.path.join(self.project_dir, ".spinetoolbox")) self.items_dir = os.path.abspath(os.path.join(self.config_dir, "items")) self.specs_dir = os.path.abspath( os.path.join(self.config_dir, "specifications")) self.config_file = os.path.abspath( os.path.join(self.config_dir, PROJECT_FILENAME)) for dir_ in (self.project_dir, self.config_dir, self.items_dir, self.specs_dir): try: create_dir(dir_) except OSError: self._logger.msg_error.emit( "Creating directory {0} failed".format(dir_)) return False return True def call_set_name(self, name): self._toolbox.undo_stack.push(SetProjectNameCommand(self, name)) def call_set_description(self, description): self._toolbox.undo_stack.push( SetProjectDescriptionCommand(self, description)) def set_name(self, name): """Changes project name. Args: name (str): New project name """ super().set_name(name) self._toolbox.update_window_title() # Remove entry with the old name from File->Open recent menu self._toolbox.remove_path_from_recent_projects(self.project_dir) # Add entry with the new name back to File->Open recent menu self._toolbox.update_recent_projects() self._logger.msg.emit("Project name changed to <b>{0}</b>".format( self.name)) def set_description(self, description): super().set_description(description) msg = "Project description " if description: msg += f"changed to <b>{description}</b>" else: msg += "cleared" self._logger.msg.emit(msg) def save(self, spec_paths): """Collects project information and objects into a dictionary and writes it to a JSON file. Args: spec_paths (dict): List of absolute paths to specification files keyed by item type Returns: bool: True or False depending on success """ project_dict = dict() # Dictionary for storing project info project_dict["version"] = LATEST_PROJECT_VERSION project_dict["name"] = self.name project_dict["description"] = self.description project_dict["specifications"] = spec_paths project_dict["connections"] = [ connection.to_dict() for connection in self._connections ] items_dict = dict() # Dictionary for storing project items # Traverse all items in project model by category for category_item in self._project_item_model.root().children(): category = category_item.name # Store item dictionaries with item name as key and item_dict as value for item in self._project_item_model.items(category): items_dict[item.name] = item.project_item.item_dict() # Save project to file saved_dict = dict(project=project_dict, items=items_dict) # Write into JSON file with open(self.config_file, "w") as fp: json.dump(saved_dict, fp, indent=4) return True def load(self, items_dict, connection_dicts): """Populates project item model with items loaded from project file. Args: items_dict (dict): Dictionary containing all project items in JSON format connection_dicts (list of dict): List containing all connections in JSON format """ self._logger.msg.emit("Loading project items...") if not items_dict: self._logger.msg_warning.emit("Project has no items") self.make_and_add_project_items(items_dict, verbosity=False) for connection in map(Connection.from_dict, connection_dicts): self.add_connection(connection) def get_item(self, name): """Returns project item. Args: name (str): item's name Returns: ProjectItem: project item """ return self._project_item_model.get_item(name).project_item def add_project_items(self, items_dict, set_selected=False, verbosity=True): """Pushes an AddProjectItemsCommand to the toolbox undo stack. """ if not items_dict: return self._toolbox.undo_stack.push( AddProjectItemsCommand(self, items_dict, set_selected=set_selected, verbosity=verbosity)) def make_project_tree_items(self, items_dict): """Creates and returns a dictionary mapping category indexes to a list of corresponding LeafProjectTreeItem instances. Args: items_dict (dict): a mapping from item name to item dict Returns: dict(QModelIndex, list(LeafProjectTreeItem)) """ project_items_by_category = {} for item_name, item_dict in items_dict.items(): item_type = item_dict["type"] factory = self._toolbox.item_factories.get(item_type) if factory is None: self._logger.msg_error.emit( f"Unknown item type <b>{item_type}</b>") self._logger.msg_error.emit( f"Loading project item <b>{item_name}</b> failed") return {} try: project_item = factory.make_item(item_name, item_dict, self._toolbox, self) except TypeError as error: self._logger.msg_error.emit( f"Creating <b>{item_type}</b> project item <b>{item_name}</b> failed. " "This is most likely caused by an outdated project file.") logging.debug(error) continue except KeyError as error: self._logger.msg_error.emit( f"Creating <b>{item_type}</b> project item <b>{item_name}</b> failed. " f"This is most likely caused by an outdated or corrupted project file " f"(missing JSON key: {str(error)}).") logging.debug(error) continue original_data_dir = item_dict.get("original_data_dir") original_db_url = item_dict.get("original_db_url") duplicate_files = item_dict.get("duplicate_files") if original_data_dir is not None and original_db_url is not None and duplicate_files is not None: project_item.copy_local_data(original_data_dir, original_db_url, duplicate_files) project_items_by_category.setdefault(project_item.item_category(), list()).append(project_item) project_tree_items = {} for category, project_items in project_items_by_category.items(): category_ind = self._project_item_model.find_category(category) # NOTE: category_ind might be None, and needs to be handled caller side project_tree_items[category_ind] = [ LeafProjectTreeItem(project_item, self._toolbox) for project_item in project_items ] return project_tree_items def do_add_project_tree_items(self, category_ind, *project_tree_items, set_selected=False, verbosity=True): """Adds LeafProjectTreeItem instances to project. Args: category_ind (QModelIndex): The category index project_tree_items (LeafProjectTreeItem): one or more LeafProjectTreeItem instances to add set_selected (bool): Whether to set item selected after the item has been added to project verbosity (bool): If True, prints message """ for project_tree_item in project_tree_items: project_item = project_tree_item.project_item self._project_item_model.insert_item(project_tree_item, category_ind) self._finish_project_item_construction(project_item) # Append new node to networkx graph self.add_to_dag(project_item.name) if verbosity: self._logger.msg.emit("{0} <b>{1}</b> added to project".format( project_item.item_type(), project_item.name)) if set_selected: item = list(project_tree_items)[-1] self.set_item_selected(item) def rename_item(self, previous_name, new_name, rename_data_dir_message): """Renames a project item Args: previous_name (str): item's current name new_name (str): item's new name rename_data_dir_message (str): message to show when renaming item's data directory Returns: bool: True if item was renamed successfully, False otherwise """ if not new_name.strip() or new_name == previous_name: return False if any(x in INVALID_CHARS for x in new_name): msg = f"<b>{new_name}</b> contains invalid characters." self._logger.error_box.emit("Invalid characters", msg) return False if self._project_item_model.find_item(new_name): msg = f"Project item <b>{new_name}</b> already exists" self._logger.error_box.emit("Invalid name", msg) return False new_short_name = shorten(new_name) if self._toolbox.project_item_model.short_name_reserved( new_short_name): msg = f"Project item using directory <b>{new_short_name}</b> already exists" self._logger.error_box("Invalid name", msg) return False item_index = self._project_item_model.find_item(previous_name) item = self._project_item_model.item(item_index).project_item if not item.rename(new_name, rename_data_dir_message): return False self._project_item_model.set_leaf_item_name(item_index, new_name) self.dag_handler.rename_node(previous_name, new_name) for connection in self._connections: if connection.source == previous_name: connection.source = new_name if connection.destination == previous_name: connection.destination = new_name self._logger.msg_success.emit( f"Project item <b>{previous_name}</b> renamed to <b>{new_name}</b>." ) return True @property def connections(self): return self._connections def add_connection(self, connection): """Adds a connection to the project. Args: connection (Connection): connection to add """ self._connections.append(connection) self.dag_handler.add_graph_edge(connection.source, connection.destination) def remove_connection(self, connection): self._connections.remove(connection) self.dag_handler.remove_graph_edge(connection.source, connection.destination) def set_item_selected(self, item): """ Selects the given item. Args: item (LeafProjectTreeItem) """ ind = self._project_item_model.find_item(item.name) self._toolbox.ui.treeView_project.setCurrentIndex(ind) def make_and_add_project_items(self, items_dict, set_selected=False, verbosity=True): """Adds items to project at loading. Args: items_dict (dict): a mapping from item name to item dict set_selected (bool): Whether to set item selected after the item has been added to project verbosity (bool): If True, prints message """ for category_ind, project_tree_items in self.make_project_tree_items( items_dict).items(): self.do_add_project_tree_items(category_ind, *project_tree_items, set_selected=set_selected, verbosity=verbosity) def add_to_dag(self, item_name): """Add new node (project item) to the directed graph.""" self.dag_handler.add_dag_node(item_name) def remove_all_items(self): """Pushes a RemoveAllProjectItemsCommand to the toolbox undo stack.""" items_per_category = self._project_item_model.items_per_category() if not any(v for v in items_per_category.values()): self._logger.msg.emit("No project items to remove") return delete_data = int( self._settings.value("appSettings/deleteData", defaultValue="0")) != 0 msg = "Remove all items from project?" if not delete_data: msg += "Item data directory will still be available in the project directory after this operation." else: msg += "<br><br><b>Warning: Item data will be permanently lost after this operation.</b>" message_box = QMessageBox( QMessageBox.Question, "Remove All Items", msg, buttons=QMessageBox.Ok | QMessageBox.Cancel, parent=self._toolbox, ) message_box.button(QMessageBox.Ok).setText("Remove Items") answer = message_box.exec_() if answer != QMessageBox.Ok: return links = self._toolbox.ui.graphicsView.links() self._toolbox.undo_stack.push( RemoveAllProjectItemsCommand(self, items_per_category, links, delete_data=delete_data)) def remove_project_items(self, *indexes, ask_confirmation=False): """Pushes a RemoveProjectItemsCommand to the toolbox undo stack. Args: *indexes (QModelIndex): Indexes of the items in project item model ask_confirmation (bool): If True, shows 'Are you sure?' message box """ indexes = list(indexes) delete_data = int( self._settings.value("appSettings/deleteData", defaultValue="0")) != 0 if ask_confirmation: names = ", ".join(ind.data() for ind in indexes) msg = f"Remove item(s) <b>{names}</b> from project? " if not delete_data: msg += "Item data directory will still be available in the project directory after this operation." else: msg += "<br><br><b>Warning: Item data will be permanently lost after this operation.</b>" msg += "<br><br>Tip: Remove items by pressing 'Delete' key to bypass this dialog." # noinspection PyCallByClass, PyTypeChecker message_box = QMessageBox( QMessageBox.Question, "Remove Item", msg, buttons=QMessageBox.Ok | QMessageBox.Cancel, parent=self._toolbox, ) message_box.button(QMessageBox.Ok).setText("Remove Item") answer = message_box.exec_() if answer != QMessageBox.Ok: return self._toolbox.undo_stack.push( RemoveProjectItemsCommand(self, *indexes, delete_data=delete_data)) def do_remove_project_tree_items(self, category_ind, *items, delete_data=False): """Removes LeafProjectTreeItem from project. Args: category_ind (QModelIndex): The category index *items (LeafProjectTreeItem): the items to remove delete_data (bool): If set to True, deletes the directories and data associated with the item """ items = list(items) for item in items: # Remove item from project model self._project_item_model.remove_item(item, parent=category_ind) # Remove item icon and connected links (QGraphicsItems) from scene icon = item.project_item.get_icon() self._toolbox.ui.graphicsView.remove_icon(icon) self.dag_handler.remove_node_from_graph(item.name) item.project_item.tear_down() if delete_data: try: data_dir = item.project_item.data_dir except AttributeError: data_dir = None if data_dir: # Remove data directory and all its contents self._logger.msg.emit( "Removing directory <b>{0}</b>".format(data_dir)) try: if not erase_dir(data_dir): self._logger.msg_error.emit( "Directory does not exist") except OSError: self._logger.msg_error.emit( "[OSError] Removing directory failed. Check directory permissions." ) self._logger.msg.emit( f"Item(s) <b>{', '.join(item.name for item in items)}</b> removed from project" ) def execute_dags(self, dags, execution_permits, msg): """Executes given dags. Args: dags (Sequence(DiGraph)) execution_permits (Sequence(dict)) """ self.project_execution_about_to_start.emit() self._logger.msg.emit("") self._logger.msg.emit( "-------------------------------------------------") self._logger.msg.emit(f"<b>{msg}</b>") self._logger.msg.emit( "-------------------------------------------------") self._execution_stopped = False self._execute_dags(dags, execution_permits) def get_node_successors(self, dag, dag_identifier): node_successors = self.dag_handler.node_successors(dag) if not node_successors: self._logger.msg_warning.emit( "<b>Graph {0} is not a Directed Acyclic Graph</b>".format( dag_identifier)) self._logger.msg.emit("Items in graph: {0}".format(", ".join( dag.nodes()))) edges = [ "{0} -> {1}".format(*edge) for edge in self.dag_handler.edges_causing_loops(dag) ] self._logger.msg.emit( "Please edit connections in Design View to execute it. " "Possible fix: remove connection(s) {0}.".format( ", ".join(edges))) return None return node_successors def _execute_dags(self, dags, execution_permits_list): if self._engine_workers: self._logger.msg_error.emit("Execution already in progress.") return settings = make_settings_dict_for_engine(self._settings) for k, (dag, execution_permits) in enumerate( zip(dags, execution_permits_list)): dag_identifier = f"{k + 1}/{len(dags)}" worker = self.create_engine_worker(dag, execution_permits, dag_identifier, settings) worker.finished.connect(lambda worker=worker: self. _handle_engine_worker_finished(worker)) self._engine_workers.append(worker) # NOTE: Don't start the workers as they are created. They may finish too quickly, before the others # are added to ``_engine_workers``, and thus ``_handle_engine_worker_finished()`` will believe # that the project is done executing before it's fully loaded. for worker in self._engine_workers: self._logger.msg.emit("<b>Starting DAG {0}</b>".format( worker.dag_identifier)) self._logger.msg.emit("Order: {0}".format(" -> ".join( worker.engine_data["node_successors"]))) worker.start() def create_engine_worker(self, dag, execution_permits, dag_identifier, settings): node_successors = self.get_node_successors(dag, dag_identifier) if node_successors is None: return project_items = { name: self._project_item_model.get_item(name).project_item for name in node_successors } items = {} specifications = {} for name, project_item in project_items.items(): items[name] = project_item.item_dict() spec = project_item.specification() if spec is not None: spec_dict = spec.to_dict().copy() spec_dict["definition_file_path"] = spec.definition_file_path specifications.setdefault(project_item.item_type(), list()).append(spec_dict) connections = [c.to_dict() for c in self._connections] data = { "items": items, "specifications": specifications, "connections": connections, "node_successors": node_successors, "execution_permits": execution_permits, "settings": settings, "project_dir": self.project_dir, } server_address = self._settings.value( "appSettings/engineServerAddress", defaultValue="") worker = SpineEngineWorker(server_address, data, dag, dag_identifier, project_items) return worker def _handle_engine_worker_finished(self, worker): finished_outcomes = { "USER_STOPPED": "stopped by the user", "FAILED": "failed", "COMPLETED": "completed successfully", } outcome = finished_outcomes.get(worker.engine_final_state()) if outcome is not None: self._logger.msg.emit("<b>DAG {0} {1}</b>".format( worker.dag_identifier, outcome)) if any(worker.engine_final_state() not in finished_outcomes for worker in self._engine_workers): return # Only after all workers have finished, notify changes and handle successful executions. # Doing it *while* executing leads to deadlocks at acquiring sqlalchemy's infamous _CONFIGURE_MUTEX # (needed to create DatabaseMapping instances). It seems that the lock gets confused when # being acquired by threads from different processes or maybe even different QThreads. # Can't say I really understand the whole extent of it. for finished_worker in self._engine_workers: self.notify_changes_in_dag(finished_worker.dag) for item, direction, state in finished_worker.successful_executions: item.handle_execution_successful(direction, state) finished_worker.clean_up() self._engine_workers.clear() self.project_execution_finished.emit() def dag_with_node(self, item_name): dag = self.dag_handler.dag_with_node(item_name) if not dag: self._logger.msg_error.emit( "[BUG] Could not find a graph containing {0}. " "<b>Please reopen the project.</b>".format(item_name)) return dag def execute_selected(self): """Executes DAGs corresponding to all selected project items.""" if not self.dag_handler.dags(): self._logger.msg_warning.emit("Project has no items to execute") return # Get selected item selected_indexes = self._toolbox.ui.treeView_project.selectedIndexes() if not selected_indexes: self._logger.msg_warning.emit( "Please select a project item and try again.") return dags = set() executable_item_names = list() for ind in selected_indexes: item = self._project_item_model.item(ind) executable_item_names.append(item.name) dag = self.dag_with_node(item.name) if not dag: continue dags.add(dag) execution_permit_list = list() for dag in dags: execution_permits = dict() for item_name in dag.nodes: execution_permits[ item_name] = item_name in executable_item_names execution_permit_list.append(execution_permits) self.execute_dags(dags, execution_permit_list, "Executing Selected Directed Acyclic Graphs") def execute_project(self): """Executes all dags in the project.""" dags = self.dag_handler.dags() if not dags: self._logger.msg_warning.emit("Project has no items to execute") return execution_permit_list = list() for dag in dags: execution_permit_list.append( {item_name: True for item_name in dag.nodes}) self.execute_dags(dags, execution_permit_list, "Executing All Directed Acyclic Graphs") def stop(self): """Stops execution. Slot for the main window Stop tool button in the toolbar.""" if self._execution_stopped: self._logger.msg.emit("No execution in progress") return self._logger.msg.emit("Stopping...") self._execution_stopped = True # Stop experimental engines for worker in self._engine_workers: worker.stop_engine() def export_graphs(self): """Exports all valid directed acyclic graphs in project to GraphML files.""" if not self.dag_handler.dags(): self._logger.msg_warning.emit("Project has no graphs to export") return i = 0 for g in self.dag_handler.dags(): fn = str(i) + ".graphml" path = os.path.join(self.project_dir, fn) if not self.dag_handler.export_to_graphml(g, path): self._logger.msg_warning.emit( "Exporting graph nr. {0} failed. Not a directed acyclic graph" .format(i)) else: self._logger.msg.emit("Graph nr. {0} exported to {1}".format( i, path)) i += 1 @Slot(object) def notify_changes_in_dag(self, dag): """Notifies the items in given dag that the dag has changed.""" # We wait 100 msecs before do the notification. This is to avoid notifying multiple # times the same dag, when multiple items in that dag change. if dag in self._dags_about_to_be_notified: return self._dags_about_to_be_notified.add(dag) QTimer.singleShot(100, lambda dag=dag: self._do_notify_changes_in_dag(dag)) def _do_notify_changes_in_dag(self, dag): self._dags_about_to_be_notified.remove(dag) node_successors = self.dag_handler.node_successors(dag) items = { item.name: item.project_item for item in self._project_item_model.items() } if not node_successors: # Not a dag, invalidate workflow edges = self.dag_handler.edges_causing_loops(dag) for node in dag.nodes(): items[node].invalidate_workflow(edges) return # Make resource map and run simulation node_predecessors = inverted(node_successors) ranks = _ranks(node_successors) # Memoize resources, so we don't call multiple times the same function resources_for_direct_successors = {} resources_for_direct_predecessors = {} for item_name, child_names in node_successors.items(): item = items[item_name] upstream_resources = [] downstream_resources = [] for parent_name in node_predecessors.get(item_name, set()): parent_item = items[parent_name] if parent_item not in resources_for_direct_successors: resources_for_direct_successors[ parent_item] = parent_item.resources_for_direct_successors( ) upstream_resources += resources_for_direct_successors[ parent_item] for child_name in child_names: child_item = items[child_name] if child_item not in resources_for_direct_predecessors: resources_for_direct_predecessors[ child_item] = child_item.resources_for_direct_predecessors( ) downstream_resources += resources_for_direct_predecessors[ child_item] item.handle_dag_changed(ranks[item_name], upstream_resources, downstream_resources) if item_name not in resources_for_direct_successors: resources_for_direct_successors[ item_name] = item.resources_for_direct_successors() for link in item.get_icon().outgoing_links(): link.handle_dag_changed( resources_for_direct_successors[item_name]) def notify_changes_in_all_dags(self): """Notifies all items of changes in all dags in the project.""" for g in self.dag_handler.dags(): self.notify_changes_in_dag(g) def notify_changes_in_containing_dag(self, item): """Notifies items in dag containing the given item that the dag has changed.""" dag = self.dag_handler.dag_with_node(item) # Some items trigger this method while they are being initialized # but before they have been added to any DAG. # In those cases we don't need to notify other items. if dag: self.notify_changes_in_dag(dag) def is_busy(self): """Queries if project is busy processing something. Returns: bool: True if project is busy, False otherwise """ return bool(self._dags_about_to_be_notified) @property def settings(self): return self._settings def _finish_project_item_construction(self, project_item): """ Activates the given project item so it works with the given toolbox. This is mainly intended to facilitate adding items back with redo. Args: project_item (ProjectItem) """ icon = project_item.get_icon() if icon is not None: icon.activate() else: icon = self._toolbox.project_item_icon(project_item.item_type()) project_item.set_icon(icon) properties_ui = self._toolbox.project_item_properties_ui( project_item.item_type()) project_item.set_properties_ui(properties_ui) project_item.set_up() def tear_down(self): """Cleans up project.""" for category_ind, project_tree_items in self._project_item_model.items_per_category( ).items(): self.do_remove_project_tree_items(category_ind, *project_tree_items, delete_data=False)
class WImportLinspace(Ui_WImportLinspace, QWidget): import_name = "Define as Linspace" import_type = ImportGenVectLin saveNeeded = Signal() dataTypeChanged = Signal() def __init__(self, parent=None, data=None, verbose_name="", expected_shape=None): """Initialization of the widget Parameters ---------- data : ImportGenVectLin Data import to define verbose_name : str Name of the imported data expected_shape : list List to enforce a shape, [None, 2] enforce 2D matrix with 2 columns """ QWidget.__init__(self, parent=parent) self.setupUi(self) if data is None: self.data = ImportGenVectLin() else: self.data = data self.verbose_name = verbose_name self.expected_shape = expected_shape self.update() # Connect the slot/signal self.lf_start.editingFinished.connect(self.set_start) self.lf_stop.editingFinished.connect(self.set_stop) self.si_N.editingFinished.connect(self.set_N) self.is_end.toggled.connect(self.set_is_end) self.c_type_lin.currentIndexChanged.connect(self.set_type_lin) def update(self): """Fill the widget with the current value of the data""" self.c_type_lin.setCurrentIndex(0) # Start, Stop, N self.set_type_lin() self.lf_start.setValue(self.data.start) self.lf_stop.setValue(self.data.stop) if self.data.num is None: self.data.num = 100 self.si_N.setValue(self.data.num) if self.data.endpoint: self.is_end.setCheckState(Qt.Checked) else: self.is_end.setCheckState(Qt.Unchecked) def set_type_lin(self): if self.c_type_lin.currentIndex() == 0: self.in_N.show() self.si_N.show() self.in_step.hide() self.lf_step.hide() else: self.in_N.hide() self.si_N.hide() self.in_step.show() self.lf_step.show() def set_start(self): """Change the value according to the widget""" self.data.start = self.lf_start.value() # Notify the machine GUI that the machine has changed self.saveNeeded.emit() def set_stop(self): """Change the value according to the widget""" self.data.stop = self.lf_stop.value() # Notify the machine GUI that the machine has changed self.saveNeeded.emit() def set_N(self): """Change the value according to the widget""" self.data.num = self.si_N.value() # Notify the machine GUI that the machine has changed self.saveNeeded.emit() def set_is_end(self, is_checked): """Signal to update the value of is_internal according to the widget Parameters ---------- self : WImportLinspace A WImportLinspace object is_checked : bool State of is_internal """ self.data.endpoint = is_checked # Notify the machine GUI that the machine has changed self.saveNeeded.emit()
class Standart(QThread): def __init__(self): QThread.__init__(self) #АТРИБУТ СТОП self.running = False modPath = Signal(str, arguments=['modpath']) @Slot(str) #ПУТЬ ФИНАЛЬНОГО ФАЙЛА def make_path(self, path): head, tail = os.path.split(path) head = re.sub(tail, '', path, flags=re.MULTILINE) file_path = head + 'OUTput.txt' self.modPath.emit(file_path) @Slot(str) #ТЕСТ КОДИРОВКИ def test_codirovka(self,file,): file = file.lstrip("file:///") status = "True" try: with open(file,'r', encoding='utf-8') as f: opentxtfile = f.read() except: status = "False" self.forTectcoding.emit(status) #ОТКРЫТЬ ФАЙЛ def openfile(self, file): with open(file,'r', encoding='utf-8') as f: opentxtfile = f.read() return opentxtfile #ОТКРЫТЬ ФАЙЛ СПИСОК def openfilelist(self, file): with open(file,'r', encoding='utf-8') as f: opentxtfile = f.readlines() return opentxtfile #ОПРЕДЕЛЯЕМ КОЛИЧЕСТВО СТРОК В ФАЙЛЕ def skolstrok(self, file): with open(file,'r', encoding='utf-8') as f: return len(f.readlines()) #ЗАПИСАТЬ В ФАЙЛ def writefile(self, file,metod,zapis): with open(file,metod, encoding='utf-8') as f: opentxtfile = f.write(zapis) return opentxtfile nowProgbar = Signal(int, arguments=['nowprogbar']) maxProgbar = Signal(int, arguments=['maxprogbar']) #СИГНАЛ ОКОНЧАНИЯ ПРОЦЕССА allOk = Signal(str, arguments=['allok']) #ТЕСТ КОДИРОВКИ forTectcoding = Signal(str, arguments=['fortectcoding']) @Slot(str, str, str, str) #ОБРАБОТЧИК (file_path - переменные, file_path2 - заготова, file_path3 - финал) def felico(self, file_path, file_path2, file_path3, chtomenaem): file_path = file_path.lstrip("file:///") file_path2 = file_path2.lstrip("file:///") file_path3 = file_path3.lstrip("file:///") #ДЛЯ ОСТАНОВКИ ЦИКЛА self.running = True max_progbar = self.skolstrok(file_path) #ДЛЯ ПРОГРЕССБАРА MAX VALUE self.maxProgbar.emit(max_progbar) #ДЛЯ ПРОГРЕССБАРА MIN VALUE now_progbar = 0 #ЛИСТ ПЕРЕМЕННЫХ ДЛЯ ЗАМЕНЫ lst_zamena = self.openfilelist(file_path) lst_zamena = [i.rstrip() for i in lst_zamena] for i in range(self.skolstrok(file_path)): #ДЛЯ ОСТАНОВКИ ЦИКЛА if self.running == False: break #МЕНЯЕМ replaceok = self.openfile(file_path2).replace(chtomenaem,lst_zamena.pop(0)) if replaceok.endswith('\n') == False: replaceok +='\n' #ЗАПИСЫВАЕМ В ФАЙЛ ГОТОВО self.writefile(file_path3,'a',replaceok) now_progbar +=1 self.nowProgbar.emit(now_progbar) #ПРОГРЕССБАР НА 0 self.nowProgbar.emit(0) self.allOk.emit("OK")
class ParameterMergingSettingsWindow(QWidget): """A window which shows a list of ParameterMergingSettings widgets, one for each merged parameter.""" settings_approved = Signal() """Emitted when the settings have been approved.""" settings_rejected = Signal() """Emitted when the settings have been rejected.""" def __init__(self, merging_settings, database_path, parent): """ Args: merging_settings (dict): a map from merged parameter name to merging settings database_path (str): database URL parent (QWidget): a parent widget """ from ..ui.parameter_merging_settings_window import Ui_Form # pylint: disable=import-outside-toplevel super().__init__(parent, f=Qt.Window) self._merging_settings = merging_settings self._entity_class_infos = None self._database_url = database_path self._ui = Ui_Form() self._ui.setupUi(self) self.setWindowTitle("Gdx Parameter Merging Settings -- {} --".format(database_path)) self._setting_widgets = list() for parameter_name, setting in merging_settings.items(): self._add_setting(parameter_name, setting) self._ui.button_box.accepted.connect(self._collect_and_hide) self._ui.button_box.rejected.connect(self._reject_and_close) self._ui.add_button.clicked.connect(self._add_empty_setting) @property def merging_settings(self): """a dict that maps merged parameter names to their merging settings""" return self._merging_settings def update(self): """Updates the settings according to changes in the database.""" db_map = DatabaseMapping(self._database_url) try: self._entity_class_infos = _gather_entity_class_infos(db_map) finally: db_map.connection.close() for settings_widget in self._setting_widgets: settings_widget.update(self._entity_class_infos) def _add_setting(self, parameter_name=None, merging_setting=None): """Inserts a new settings widget to the widget list.""" if self._entity_class_infos is None: db_map = DatabaseMapping(self._database_url) try: self._entity_class_infos = _gather_entity_class_infos(db_map) finally: db_map.connection.close() settings_widget = ParameterMergingSettings(self._entity_class_infos, self, parameter_name, merging_setting) settings_widget.removal_requested.connect(self._remove_setting) self._ui.settings_area_layout.insertWidget(0, settings_widget) self._setting_widgets.append(settings_widget) def _ok_to_accept(self): """Returns True if it is OK to accept the settings, otherwise shows a warning dialog and returns False.""" for settings_widget in self._setting_widgets: flags = settings_widget.error_flags if flags & MergingErrorFlag.PARAMETER_NAME_MISSING: self._ui.setting_area.ensureWidgetVisible(settings_widget) message = "Parameter name is missing." QMessageBox.warning(self, "Parameter Name Missing", message) return False if flags & MergingErrorFlag.DOMAIN_NAME_MISSING: self._ui.setting_area.ensureWidgetVisible(settings_widget) message = "Domain name is missing." QMessageBox.warning(self, "Domain Name Missing", message) return False if flags & MergingErrorFlag.NO_PARAMETER_SELECTED: self._ui.setting_area.ensureWidgetVisible(settings_widget) message = "No domain selected for parameter merging." QMessageBox.warning(self, "Domain Selection Missing", message) return False return True @Slot(bool) def _add_empty_setting(self, _): """Adds an empty settings widget to the widget list.""" self._add_setting() @Slot("QVariant") def _remove_setting(self, settings_widget): """Removes a setting widget from the widget list.""" self._setting_widgets.remove(settings_widget) self._ui.settings_area_layout.removeWidget(settings_widget) settings_widget.deleteLater() @Slot() def _collect_and_hide(self): """Collects settings from individual ParameterMergingSettings widgets and hides the window.""" if not self._ok_to_accept(): return self._merging_settings = {widget.parameter_name: widget.merging_setting() for widget in self._setting_widgets} self.settings_approved.emit() self.hide() @Slot() def _reject_and_close(self): """Emits settings_rejected and closes the window.""" self.settings_rejected.emit() self.close()
class ColorPanelHSB(QWidget): """ 颜色选取面板 作者:feiyangqingyun(QQ:517216493) 2017-11-17 译者:sunchuquin(QQ:1715216365) 2021-07-03 1. 可设置当前百分比,用于控制指针大小 2. 可设置边框宽度 3. 可设置边框颜色 4. 可设置指针颜色 """ colorChanged = Signal(QColor, float, float) # color, hue, sat def __init__(self, parent: QWidget = None): super(ColorPanelHSB, self).__init__(parent) self.__percent: int = 100 # 当前百分比 self.__borderWidth: int = 10 # 边框宽度 self.__borderColor: QColor = QColor(0, 0, 0, 50) # 边框颜色 self.__cursorColor: QColor = QColor(0, 0, 0) # 鼠标按下处的文字形状颜色 self.__color: QColor = QColor(255, 0, 0) # 鼠标按下处的颜色 self.__hue: float = 0 # hue值 self.__sat: float = 100 # sat值 self.__lastPos: QPoint = QPoint(self.__borderWidth, self.__borderWidth) # 最后鼠标按下去的坐标 self.__bgPix: QPixmap = QPixmap() # 背景颜色图片 def showEvent(self, event: QShowEvent = None) -> None: width: int = self.width() height: int = self.height() # 首次显示记住当前背景截图,用于获取颜色值 self.__bgPix = QPixmap(width, height) self.__bgPix.fill(Qt.transparent) painter: QPainter = QPainter() painter.begin(self.__bgPix) colorStart: QColor = QColor() colorCenter: QColor = QColor() colorEnd: QColor = QColor() for i in range(width): colorStart.setHslF(i / width, 1, 0) colorCenter.setHslF(i / width, 1, 0.5) colorEnd.setHslF(i / width, 1, 1) linearGradient: QLinearGradient = QLinearGradient() linearGradient.setStart(QPointF(i, -height)) linearGradient.setFinalStop(QPointF(i, height)) linearGradient.setColorAt(0.0, colorStart) linearGradient.setColorAt(0.5, colorCenter) linearGradient.setColorAt(1.0, colorEnd) painter.setPen(QPen(linearGradient, 1)) painter.drawLine(QPointF(i, 0), QPointF(i, height)) painter.end() def resizeEvent(self, event: QResizeEvent = None) -> None: self.showEvent() def mousePressEvent(self, event: QMouseEvent = None) -> None: self.mouseMoveEvent(event) def mouseMoveEvent(self, event: QMouseEvent = None) -> None: x: int = event.pos().x() y: int = event.pos().y() # 矫正X轴的偏差 if x <= self.__borderWidth: x = self.__borderWidth elif x >= self.width() - self.__borderWidth: x = self.width() - self.__borderWidth # 矫正Y轴的偏差 if y <= self.__borderWidth: y = self.__borderWidth elif y >= self.height() - self.__borderWidth: y = self.height() - self.__borderWidth # 指针必须在范围内 self.__lastPos = QPoint(x, y) # 获取当前坐标处的颜色值 self.__color = QColor(self.__bgPix.toImage().pixel(self.__lastPos)) # X坐标所在360分比为hue值 self.__hue = ((x - self.__borderWidth) / (self.width() - self.__borderWidth * 2)) * 360 # Y坐标所在高度的100分比sat值 self.__sat = 100 - ((y - self.__borderWidth) / (self.height() - self.__borderWidth * 2) * 100) self.update() self.colorChanged.emit(self.__color, self.__hue, self.__sat) def paintEvent(self, event: QPaintEvent = None) -> None: # 绘制准备工作,启用反锯齿 painter: QPainter = QPainter(self) painter.setRenderHints(QPainter.Antialiasing | QPainter.TextAntialiasing) # 绘制背景颜色 self.drawBg(painter) # 绘制按下出的形状 self.drawCursor(painter) # 绘制边框 self.drawBorder(painter) def drawBg(self, painter: QPainter = None) -> None: painter.save() if not self.__bgPix.isNull(): painter.drawPixmap(0, 0, self.__bgPix) painter.restore() def drawCursor(self, painter: QPainter = None) -> None: painter.save() painter.setPen(self.__cursorColor) text: str = "+" # 根据右侧的百分比显示字体大小 textFont: QFont = QFont() size: int = int(20 + (35 * self.__percent / 100)) textFont.setPixelSize(size) # 计算文字的宽度高度,自动移到鼠标按下处的中心点 fm: QFontMetrics = QFontMetrics(textFont) textWidth: int = fm.width(text) textHeight: int = fm.height() textPoint: QPoint = self.__lastPos - QPoint(textWidth // 2, -(textHeight // 4)) path: QPainterPath = QPainterPath() path.addText(textPoint, textFont, text) painter.drawPath(path) painter.restore() def drawBorder(self, painter: QPainter = None) -> None: painter.save() width: int = self.width() height: int = self.height() offset: int = self.__borderWidth pen: QPen = QPen() pen.setWidth(offset) pen.setColor(self.__borderColor) pen.setCapStyle(Qt.RoundCap) pen.setJoinStyle(Qt.MiterJoin) painter.setPen(pen) painter.setBrush(Qt.NoBrush) painter.drawRect(offset // 2, offset // 2, width - offset, height - offset) painter.restore() def sizeHint(self) -> QSize: return QSize(500, 350) def minimumSizeHint(self) -> QSize: return QSize(100, 60) @property def percent(self) -> int: return self.__percent @percent.setter def percent(self, n_percent: int) -> None: if self.__percent == n_percent: return self.__percent = n_percent self.update() @property def borderColor(self) -> QColor: return self.__borderColor @borderColor.setter def borderColor(self, border_color: QColor) -> None: if self.__borderColor == border_color: return self.__borderColor = border_color self.update() @property def cursorColor(self) -> QColor: return self.__cursorColor @cursorColor.setter def cursorColor(self, cursor_color: QColor) -> None: if self.__cursorColor == cursor_color: return self.__cursorColor = cursor_color self.update() @property def color(self) -> QColor: return self.__color @property def hue(self) -> float: return self.__hue @property def sat(self) -> float: return self.__sat
class WPathSelector(Ui_WPathSelector, QWidget): """Widget to select the path to a file or a folder""" pathChanged = Signal() # Changed and correct def __init__(self, parent=None): """Create the widget Parameters ---------- self : WPathSelector A WPathSelector object parent : QWidget A reference to the widgets parent """ # Build the interface according to the .ui file QWidget.__init__(self, parent) self.setupUi(self) # Create the property of the widget self.obj = None # object that has a path property to set self.verbose_name = ( "" # Name to display in the GUI (leave empty to use param_name) ) self.param_name = "" # path property name self.is_file = True # True path to a file, False path to a folder self.extension = "" # Filter file type # Connect the slot/signals self.le_path.editingFinished.connect(self.set_obj_path) self.b_path.clicked.connect(self.select_path) def update(self): """Update the widget to match the value of the properties Parameters ---------- self : WPathSelector A WPathSelector object Returns ------- """ # Set the correct text for the label if self.verbose_name in ["", None]: self.verbose_name = self.param_name self.in_path.setText(self.verbose_name + ": ") # Set the correct text for the button if self.is_file: self.b_path.setText("Select File") else: self.b_path.setText("Select Folder") # Get the current path to display if self.obj is not None: self.le_path.setText(getattr(self.obj, self.param_name)) def get_path(self): """Return the current path""" return self.le_path.text().replace("\\", "/") def set_path_txt(self, path): """Set the line edit text""" if path is not None: path = path.replace("\\", "/") self.le_path.setText(path) def set_obj_path(self): """Update the object with the current path (if correct)""" path = self.get_path().replace("\\", "/") if (self.is_file and isfile(path)) or (not self.is_file and isdir(path)): if self.obj is not None: if getattr(self.obj, self.param_name) != path: setattr(self.obj, self.param_name, path) self.pathChanged.emit() else: self.pathChanged.emit() def select_path(self): """Open a popup to select the correct path""" # Initial folder for the Dialog default_path = self.get_path() if self.is_file: # Select a file if not isfile(default_path): default_path = "" path = QFileDialog.getOpenFileName( self, "Select " + self.verbose_name + " file", default_path, filter=self.extension, )[0] else: # Select a Folder if not isdir(default_path): default_path = "" path = QFileDialog.getExistingDirectory( self, "Select " + self.verbose_name + " directory", default_path ) # Update the path if path: # check for empty string as well as None path = path.replace("\\", "/") self.le_path.setText(path) self.set_obj_path()
class PWSlot12(Gen_PWSlot12, QWidget): """Page to set the Slot Type 12""" # Signal to DMachineSetup to know that the save popup is needed saveNeeded = Signal() # Information for Slot combobox slot_name = "Slot Type 12" slot_type = SlotW12 def __init__(self, lamination=None): """Initialize the GUI according to current lamination Parameters ---------- self : PWSlot12 A PWSlot12 widget lamination : Lamination current lamination to edit """ # Build the interface according to the .ui file QWidget.__init__(self) self.setupUi(self) self.lamination = lamination self.slot = lamination.slot # Set FloatEdit unit self.lf_R1.unit = "m" self.lf_R2.unit = "m" self.lf_H0.unit = "m" self.lf_H1.unit = "m" # Set unit name (m ou mm) wid_list = [self.unit_R1, self.unit_R2, self.unit_H0, self.unit_H1] for wid in wid_list: wid.setText(gui_option.unit.get_m_name()) # Fill the fields with the machine values (if they're filled) self.lf_R1.setValue(self.slot.R1) self.lf_R2.setValue(self.slot.R2) self.lf_H0.setValue(self.slot.H0) self.lf_H1.setValue(self.slot.H1) # Display the main output of the slot (surface, height...) self.w_out.comp_output() # Connect the signal self.lf_R1.editingFinished.connect(self.set_R1) self.lf_R2.editingFinished.connect(self.set_R2) self.lf_H0.editingFinished.connect(self.set_H0) self.lf_H1.editingFinished.connect(self.set_H1) def set_R1(self): """Signal to update the value of R1 according to the line edit Parameters ---------- self : PWSlot12 A PWSlot12 object """ self.slot.R1 = self.lf_R1.value() self.w_out.comp_output() # Notify the machine GUI that the machine has changed self.saveNeeded.emit() def set_R2(self): """Signal to update the value of R2 according to the line edit Parameters ---------- self : PWSlot12 A PWSlot12 object """ self.slot.R2 = self.lf_R2.value() self.w_out.comp_output() # Notify the machine GUI that the machine has changed self.saveNeeded.emit() def set_H0(self): """Signal to update the value of H0 according to the line edit Parameters ---------- self : PWSlot12 A PWSlot12 object """ self.slot.H0 = self.lf_H0.value() self.w_out.comp_output() # Notify the machine GUI that the machine has changed self.saveNeeded.emit() def set_H1(self): """Signal to update the value of H1 according to the line edit Parameters ---------- self : PWSlot12 A PWSlot12 object """ self.slot.H1 = self.lf_H1.value() self.w_out.comp_output() # Notify the machine GUI that the machine has changed self.saveNeeded.emit() @staticmethod def check(lam): """Check that the current lamination have all the needed field set Parameters ---------- lam: LamSlotWind Lamination to check Returns ------- error: str Error message (return None if no error) """ # Check that everything is set if lam.slot.R1 is None: return translate("You must set R1 !", "PWSlot12") elif lam.slot.R2 is None: return translate("You must set R2 !", "PWSlot12") elif lam.slot.H0 is None: return translate("You must set H0 !", "PWSlot12") elif lam.slot.H1 is None: return translate("You must set H1 !", "PWSlot12") # Check that everything is set right # Constraints try: lam.slot.check() except SlotCheckError as error: return str(error) # Output try: yoke_height = lam.comp_height_yoke() except Exception as error: return translate("Unable to compute yoke height:", "PWSlot12") + str(error) if yoke_height <= 0: return translate( "The slot height is greater than the lamination !", "PWSlot12")
class Worker(QObject): """ RSS Feeed worker, downloads all the feeds sequentially and sends the signal when the partial feed was downloaded. After the feeds are downloaded completelly, the signal is send too. TODO This should be updated to some kind of async processing instead of the sequential download one-by-one. """ rssSourceLoaded = Signal() rssDownloadComplete = Signal() rssSourceNotReadable = Signal() def __init__(self, parent=None): super().__init__(parent) self._rss = [] def do_work(self): """ Download the RSS feed via HTTP, try to decode it as UTF-8 """ logging.info("Loading feed data") with open("rss_source.xml") as source: buffer = source.read(-1) root_element = et.fromstring(buffer) self._rss = [] if root_element: logging.info(f"Got root element: {root_element}") for elem in root_element.findall(".//body/outline/outline"): logging.info(f"Got element: {elem}") rss_url = elem.attrib.get("xmlUrl") logging.info(f"RSS feed at {rss_url}") try: with urllib.request.urlopen(rss_url) as response: feed = response.read().decode("utf-8") self.rssSourceLoaded.emit() rss_root = et.fromstring(feed) for idx, title in enumerate( rss_root.findall(".//channel/item/title")): self._rss.append(title.text) logging.info(f"Feed: {title.text}") # only first 6 articles if idx > 6: break #if len(self._rss) > 2: # break except UnicodeDecodeError: logging.error( f"Cannot decode the RSS feed from {rss_url}") self.rssSourceNotReadable.emit() except urllib.error.HTTPError: logging.error( f"Cannot access the RSS feed from {rss_url}") self.rssSourceNotReadable.emit() else: logging.warning("Download complete") self.rssDownloadComplete.emit() def get_list(self): return self._rss
class PVentPolar(Gen_PVentPolar, QWidget): """Page to setup the Ventilation Polar""" # Signal to DMachineSetup to know that the save popup is needed saveNeeded = Signal() def __init__(self, lam=None, vent=None): """Initialize the widget according the current lamination Parameters ---------- self : PVentPolar A PVentPolar widget lam : Lamination current lamination to edit vent : VentPolar current ventilation to edit """ # Build the interface according to the .ui file QWidget.__init__(self) self.setupUi(self) # Set FloatEdit unit self.lf_H0.unit = "m" self.lf_D0.unit = "m" self.lam = lam self.vent = vent # Fill the fields with the machine values (if they're filled) if self.vent.Zh is None: self.vent.Zh = 8 self.si_Zh.setValue(self.vent.Zh) self.lf_H0.setValue(self.vent.H0) self.lf_D0.setValue(self.vent.D0) self.lf_W1.setValue(self.vent.W1) self.lf_Alpha0.setValue(self.vent.Alpha0) # Display the main output of the vent self.w_out.comp_output() # Set unit name (m ou mm) wid_list = [self.unit_H0, self.unit_D0] for wid in wid_list: wid.setText("[" + gui_option.unit.get_m_name() + "]") # Connect the signal self.si_Zh.editingFinished.connect(self.set_Zh) self.lf_H0.editingFinished.connect(self.set_H0) self.lf_D0.editingFinished.connect(self.set_D0) self.lf_W1.editingFinished.connect(self.set_W1) self.lf_Alpha0.editingFinished.connect(self.set_Alpha0) def set_Zh(self): """Signal to update the value of Zh according to the line edit Parameters ---------- self : PVentPolar A PVentPolar object """ self.vent.Zh = self.si_Zh.value() self.w_out.comp_output() def set_H0(self): """Signal to update the value of H0 according to the line edit Parameters ---------- self : PVentPolar A PVentPolar object """ self.vent.H0 = self.lf_H0.value() self.w_out.comp_output() def set_D0(self): """Signal to update the value of D0 according to the line edit Parameters ---------- self : PVentPolar A PVentPolar object """ self.vent.D0 = self.lf_D0.value() self.w_out.comp_output() def set_W1(self): """Signal to update the value of W1 according to the line edit Parameters ---------- self : PVentPolar A PVentPolar object """ self.vent.W1 = self.lf_W1.value() self.w_out.comp_output() def set_Alpha0(self): """Signal to update the value of Alpha0 according to the line edit Parameters ---------- self : PVentPolar A PVentPolar object """ self.vent.Alpha0 = self.lf_Alpha0.value() self.w_out.comp_output() def check(self): """Check that the current machine have all the needed field set Parameters ---------- self : PVentPolar A PVentPolar object Returns ------- error: str Error message (return None if no error) """ # Check that everything is set if self.vent.Zh is None: return self.tr("You must set Zh !") elif self.vent.H0 is None: return self.tr("You must set H0 !") elif self.vent.D0 is None: return self.tr("You must set D0 !") elif self.vent.W1 is None: return self.tr("You must set W1 !") elif self.vent.Alpha0 is None: return self.tr("You must set Alpha0 !") return None
class CreateModuleWidget(QMainWindow): # Конструктор def __init__(self, parent=None): super(CreateModuleWidget, self).__init__(parent) self.setWindowModality(Qt.ApplicationModal) self.setWindowTitle('Создание нового модуля') self.setMinimumSize(720, 540) self.central_widget = QWidget(self) self.text_editor = QTextEdit() self.type_of_crypto = QComboBox() self.init_ui() # Метод инициализация UI def init_ui(self): layout = QVBoxLayout(self.central_widget) layout.addWidget(self.text_editor) help_button = QPushButton('Помощь по синтаксису') help_button.clicked.connect(self.help_syntax) layout.addWidget(help_button) crypto_label = QLabel('Алгоритм шифрования') crypto_label.setStyleSheet('font-style: italic;') crypto_label.setAlignment(Qt.AlignCenter) layout.addWidget(crypto_label) self.type_of_crypto.addItem('XOR') self.type_of_crypto.addItem('AES-256') layout.addWidget(self.type_of_crypto) create_button = QPushButton('Сохранить модуль') create_button.setStyleSheet('font-weight: bold;') create_button.clicked.connect(self.save_module) layout.addWidget(create_button) self.text_editor.setText(create_basic_text()) cursor = QTextCursor(self.text_editor.document()) cursor.movePosition(QTextCursor.End) self.text_editor.setTextCursor(cursor) self.setCentralWidget(self.central_widget) # Объявление сигнала о создании модуля module_created = Signal(str) # Слот, обрабатывающий сохранение модуля @Slot() def save_module(self): # Запрашиваем пароль password, ok = QInputDialog().getText( self, 'Ввод пароля', 'Введите пароль для редактирования модуля:', QLineEdit.Password) # Если пользователь не отменил ввод if ok: try: # Проверяем модуль на ошибки parsed_data = parse(self.text_editor.toPlainText()) module_full_path = QFileDialog.getSaveFileName( self, filter='*.module')[0] if module_full_path.find('.module') == -1: module_full_path += '.module' with open(module_full_path, 'wb') as file: # Записываем модуль в файл, шифруя его if self.type_of_crypto.currentIndex() == 0: file.write(b'xor' + md5(password.encode('utf-8')).digest() + xor_bytes(self.text_editor.toPlainText())) else: file.write(b'aes' + md5(password.encode('utf-8')).digest() + aes_encrypt(self.text_editor.toPlainText())) # Если случилась ошибка ввода / вывода except IOError: mb = QMessageBox(self) mb.setWindowTitle('Ошибка') mb.setText('При сохранении файла модуля возникла ошибка.') mb.show() # Если в модуле есть ошибка except (SyntaxError, RuntimeError) as error: mb = QMessageBox(self) mb.setWindowTitle('Ошибка') mb.setText(str(error)) mb.show() else: # Если всё хорошо, сигнализируем об успешном создании модуля self.module_created.emit(module_full_path) self.close() # Слот, обрабатывающий запрос пользователя на помощь по синтаксису (нажатие соответствующй кнопки) @Slot() def help_syntax(self): smb = ScrollMessageBox('Помощь', get_help_text(), parent=self) smb.show()
class CourseTreeWidgetSignals(QObject): clearFileListWidget = Signal() appendRowFileListWidget = Signal(QStandardItem) addDownloadTask = Signal(str, str, str)
class VoltageCollapse(QThread): progress_signal = Signal(float) progress_text = Signal(str) done_signal = Signal() name = 'Voltage Stability' def __init__(self, circuit: MultiCircuit, options: VoltageCollapseOptions, inputs: VoltageCollapseInput): """ VoltageCollapse constructor @param circuit: NumericalCircuit instance @param options: """ QThread.__init__(self) # MultiCircuit instance self.circuit = circuit # voltage stability options self.options = options self.inputs = inputs self.results = list() self.__cancel__ = False def get_steps(self): """ List of steps """ if self.results.lambdas is not None: return ['Lambda:' + str(l) for l in self.results.lambdas] else: return list() def progress_callback(self, l): """ Send progress report :param l: lambda value :return: None """ self.progress_text.emit('Running voltage collapse lambda:' + "{0:.2f}".format(l) + '...') def run(self): """ run the voltage collapse simulation @return: """ print('Running voltage collapse...') nbus = len(self.circuit.buses) nbr = len(self.circuit.branches) self.results = VoltageCollapseResults(nbus=nbus, nbr=nbr) # compile the numerical circuit numerical_circuit = self.circuit.compile() numerical_input_islands = numerical_circuit.compute() self.results.bus_types = numerical_circuit.bus_types for nc, numerical_island in enumerate(numerical_input_islands): self.progress_text.emit('Running voltage collapse at circuit ' + str(nc) + '...') if len(numerical_island.ref) > 0: Voltage_series, Lambda_series, \ normF, success = continuation_nr(Ybus=numerical_island.Ybus, Ibus_base=numerical_island.Ibus, Ibus_target=numerical_island.Ibus, Sbus_base=self.inputs.Sbase[numerical_island.original_bus_idx], Sbus_target=self.inputs.Starget[numerical_island.original_bus_idx], V=self.inputs.Vbase[numerical_island.original_bus_idx], pv=numerical_island.pv, pq=numerical_island.pq, step=self.options.step, approximation_order=self.options.approximation_order, adapt_step=self.options.adapt_step, step_min=self.options.step_min, step_max=self.options.step_max, error_tol=self.options.error_tol, tol=self.options.tol, max_it=self.options.max_it, stop_at=self.options.stop_at, verbose=False, call_back_fx=self.progress_callback) # nbus can be zero, because all the arrays are going to be overwritten res = VoltageCollapseResults(nbus=numerical_island.nbus, nbr=numerical_island.nbr) res.voltages = np.array(Voltage_series) res.lambdas = np.array(Lambda_series) res.error = normF res.converged = bool(success) else: res = VoltageCollapseResults(nbus=numerical_island.nbus, nbr=numerical_island.nbr) res.voltages = np.array([[0] * numerical_island.nbus]) res.lambdas = np.array([[0] * numerical_island.nbus]) res.error = [0] res.converged = True if len(res.voltages) > 0: # compute the island branch results branch_res = numerical_island.compute_branch_results(res.voltages[-1]) self.results.apply_from_island(res, branch_res, numerical_island.original_bus_idx, numerical_island.original_branch_idx, nbus) else: print('No voltage values!') print('done!') self.progress_text.emit('Done!') self.done_signal.emit() def cancel(self): self.__cancel__ = True self.progress_signal.emit(0.0) self.progress_text.emit('Cancelled!') self.done_signal.emit()
class TreeModelR(TreeModel): checkChanged = Signal(int, str) def __init__(self, headers, data, tablemodel, newList, filterModel, parent=None): super(TreeModel, self).__init__(parent) rootData = [header for header in headers] self.rootItem = TreeItem(rootData) self.treeDict = data self.tablemodel = tablemodel self.newList = newList self.filterModel = filterModel self.examingParents = False self.examiningChildren = False self.setupModelData(data, self.rootItem) def setupModelData(self, data, parent): visited = {} queue = [] grandParents = {} for key in data.keys(): visited[(parent.itemData[0])] = [key] queue.append((key, parent, "")) grandParents[key] = (data[key], parent) curDict = data tempSource = "" while queue: poppedItem = queue.pop(0) child = poppedItem[0] parentOfChild = poppedItem[1] childSource = poppedItem[2] parent = parentOfChild parent.insertChildren(parent.childCount(), 1, self.rootItem.columnCount()) parent.child(parent.childCount() - 1).setData(0, child) for i in range(len(self.newList)): if child == self.newList[i]["Key"]: self.tablemodel.beginInsertRows( self.tablemodel.index( len(self.tablemodel.metadataList), 0), i, i) self.tablemodel.metadataList.append(self.newList[i]) self.tablemodel.endInsertRows() parent.child(parent.childCount() - 1).checked = self.newList[i]["Checked"] self.filterModel.checkList(self.newList[i]["Checked"], self.newList[i]["Source"]) if child in grandParents: curDict = grandParents[child][0] tempSource = childSource + child + "/" for curChild in range(grandParents[child][1].childCount()): if child == grandParents[child][1].child( curChild).itemData[0]: parent = grandParents[child][1].child(curChild) visited[(parent.itemData[0])] = [] if isinstance(curDict, dict): for key in curDict.keys(): if key not in visited[(parent.itemData[0])]: visited[(parent.itemData[0])].append(key) queue.append((key, parent, tempSource)) if (isinstance(curDict[key], dict)): grandParents[key] = (curDict[key], parent) else: pass
class PTDF(QThread): progress_signal = Signal(float) progress_text = Signal(str) done_signal = Signal() name = 'PTDF' def __init__(self, grid: MultiCircuit, options: PTDFOptions, pf_options: PowerFlowOptions, opf_results=None): """ Power Transfer Distribution Factors class constructor @param grid: MultiCircuit Object @param options: OPF options """ QThread.__init__(self) # Grid to run self.grid = grid # Options to use self.options = options # power flow options self.pf_options = pf_options self.opf_results = opf_results # OPF results self.results = None # set cancel state self.__cancel__ = False self.all_solved = True self.elapsed = 0.0 self.logger = Logger() def ptdf(self, circuit: MultiCircuit, options: PowerFlowOptions, group_mode: PtdfGroupMode, power_amount, text_func=None, prog_func=None): """ Power Transfer Distribution Factors analysis :param circuit: MultiCircuit instance :param options: power flow options :param group_mode: group mode :param power_amount: amount o power to vary in MW :param text_func: text function to display progress :param prog_func: progress function to display progress [0~100] :return: """ if text_func is not None: text_func('Compiling...') # compile to arrays numerical_circuit = compile_snapshot_circuit( circuit=circuit, apply_temperature=options.apply_temperature_correction, branch_tolerance_mode=options.branch_impedance_tolerance_mode, opf_results=self.opf_results) calculation_inputs = split_into_islands( numeric_circuit=numerical_circuit, ignore_single_node_islands=options.ignore_single_node_islands) # compute the variations delta_of_power_variations = get_ptdf_variations( circuit=circuit, numerical_circuit=numerical_circuit, group_mode=group_mode, power_amount=power_amount) # declare the PTDF results results = PTDFResults(n_variations=len(delta_of_power_variations) - 1, n_br=numerical_circuit.nbr, n_bus=numerical_circuit.nbus, br_names=numerical_circuit.branch_names, bus_names=numerical_circuit.bus_names) if text_func is not None: text_func('Running PTDF...') nvar = len(delta_of_power_variations) for v, variation in enumerate(delta_of_power_variations): # this super strange way of calling a function is done to maintain the same # call format as the multi-threading function returns = dict() power_flow_worker(variation=0, nbus=numerical_circuit.nbus, nbr=numerical_circuit.nbr, n_tr=numerical_circuit.ntr, bus_names=numerical_circuit.bus_names, branch_names=numerical_circuit.branch_names, transformer_names=numerical_circuit.tr_names, bus_types=numerical_circuit.bus_types, calculation_inputs=calculation_inputs, options=options, dP=variation.dP, return_dict=returns) pf_results, log = returns[0] results.logger += log # add the power flow results if v == 0: results.default_pf_results = pf_results else: results.add_results_at(v - 1, pf_results, variation) if prog_func is not None: p = (v + 1) / nvar * 100.0 prog_func(p) if self.__cancel__: break return results def ptdf_multi_treading(self, circuit: MultiCircuit, options: PowerFlowOptions, group_mode: PtdfGroupMode, power_amount, text_func=None, prog_func=None): """ Power Transfer Distribution Factors analysis :param circuit: MultiCircuit instance :param options: power flow options :param group_mode: ptdf grouping mode :param power_amount: amount o power to vary in MW :param text_func: :param prog_func :return: """ if text_func is not None: text_func('Compiling...') # compile to arrays # numerical_circuit = circuit.compile_snapshot() # calculation_inputs = numerical_circuit.compute(apply_temperature=options.apply_temperature_correction, # branch_tolerance_mode=options.branch_impedance_tolerance_mode, # ignore_single_node_islands=options.ignore_single_node_islands) numerical_circuit = compile_snapshot_circuit( circuit=circuit, apply_temperature=options.apply_temperature_correction, branch_tolerance_mode=options.branch_impedance_tolerance_mode, opf_results=self.opf_results) calculation_inputs = split_into_islands( numeric_circuit=numerical_circuit, ignore_single_node_islands=options.ignore_single_node_islands) # compute the variations delta_of_power_variations = get_ptdf_variations( circuit=circuit, numerical_circuit=numerical_circuit, group_mode=group_mode, power_amount=power_amount) # declare the PTDF results results = PTDFResults(n_variations=len(delta_of_power_variations) - 1, n_br=numerical_circuit.nbr, n_bus=numerical_circuit.nbus, br_names=numerical_circuit.branch_names, bus_names=numerical_circuit.bus_names) if text_func is not None: text_func('Running PTDF...') jobs = list() n_cores = multiprocessing.cpu_count() manager = multiprocessing.Manager() return_dict = manager.dict() # for v, variation in enumerate(delta_of_power_variations): v = 0 nvar = len(delta_of_power_variations) while v < nvar: k = 0 # launch only n_cores jobs at the time while k < n_cores + 2 and (v + k) < nvar: # run power flow at the circuit p = multiprocessing.Process( target=power_flow_worker, args=(v, numerical_circuit.nbus, numerical_circuit.nbr, numerical_circuit.ntr, numerical_circuit.bus_names, numerical_circuit.branch_names, numerical_circuit.tr_names, numerical_circuit.bus_types, calculation_inputs, options, delta_of_power_variations[v].dP, return_dict)) jobs.append(p) p.start() v += 1 k += 1 if self.__cancel__: break # wait for all jobs to complete for process_ in jobs: process_.join() # emit the progress if prog_func is not None: p = (v + 1) / nvar * 100.0 prog_func(p) if self.__cancel__: break if text_func is not None: text_func('Collecting results...') # gather the results if not self.__cancel__: for v in range(nvar): pf_results, log = return_dict[v] results.logger += log if v == 0: results.default_pf_results = pf_results else: results.add_results_at(v - 1, pf_results, delta_of_power_variations[v]) return results def run(self): """ Run thread """ start = time.time() if self.options.use_multi_threading: self.results = self.ptdf_multi_treading( circuit=self.grid, options=self.pf_options, group_mode=self.options.group_mode, power_amount=self.options.power_increment, text_func=self.progress_text.emit, prog_func=self.progress_signal.emit) else: self.results = self.ptdf(circuit=self.grid, options=self.pf_options, group_mode=self.options.group_mode, power_amount=self.options.power_increment, text_func=self.progress_text.emit, prog_func=self.progress_signal.emit) if not self.__cancel__: self.results.consolidate() end = time.time() self.elapsed = end - start self.progress_text.emit('Done!') self.done_signal.emit() def get_steps(self): """ Get variations list of strings """ if self.results is not None: return [v.name for v in self.results.variations] else: return list() def cancel(self): self.__cancel__ = True
class PyQmlProxy(QObject): # SIGNALS # Project projectCreatedChanged = Signal() projectInfoChanged = Signal() stateChanged = Signal(bool) # Fitables parametersAsObjChanged = Signal() parametersAsXmlChanged = Signal() # Structure structureParametersChanged = Signal() structureViewChanged = Signal() phasesAsObjChanged = Signal() phasesAsXmlChanged = Signal() phasesAsCifChanged = Signal() currentPhaseChanged = Signal() phasesEnabled = Signal() # Experiment patternParametersAsObjChanged = Signal() instrumentParametersAsObjChanged = Signal() instrumentParametersAsXmlChanged = Signal() experimentDataChanged = Signal() experimentDataAsXmlChanged = Signal() experimentLoadedChanged = Signal() experimentSkippedChanged = Signal() # Analysis simulationParametersChanged = Signal() fitResultsChanged = Signal() fitFinishedNotify = Signal() stopFit = Signal() currentMinimizerChanged = Signal() currentMinimizerMethodChanged = Signal() currentCalculatorChanged = Signal() # Plotting current3dPlottingLibChanged = Signal() htmlExportingFinished = Signal(bool, str) # Status info statusInfoChanged = Signal() # Undo Redo undoRedoChanged = Signal() # Misc dummySignal = Signal() # METHODS def __init__(self, parent=None): super().__init__(parent) # Initialize logics self.stateChanged.connect(self._onStateChanged) # initialize the logic controller self.lc = LogicController(self) # Structure self.structureParametersChanged.connect( self._onStructureParametersChanged) self.structureParametersChanged.connect( self.lc.chartsLogic._onStructureViewChanged) self.structureParametersChanged.connect( self.lc.state._updateCalculatedData()) self.structureViewChanged.connect( self.lc.chartsLogic._onStructureViewChanged) self.lc.phaseAdded.connect(self._onPhaseAdded) self.lc.phaseAdded.connect(self.phasesEnabled) self.currentPhaseChanged.connect(self._onCurrentPhaseChanged) self.current3dPlottingLibChanged.connect( self.onCurrent3dPlottingLibChanged) # Experiment self.experimentDataChanged.connect(self._onExperimentDataChanged) self.experimentLoadedChanged.connect(self._onExperimentLoadedChanged) self.experimentSkippedChanged.connect(self._onExperimentSkippedChanged) # Analysis self.simulationParametersChanged.connect( self._onSimulationParametersChanged) self.simulationParametersChanged.connect(self.undoRedoChanged) self.currentMinimizerChanged.connect(self._onCurrentMinimizerChanged) self.currentMinimizerMethodChanged.connect( self._onCurrentMinimizerMethodChanged) # Status info self.statusInfoChanged.connect(self._onStatusInfoChanged) self.currentCalculatorChanged.connect(self.statusInfoChanged) #self.currentCalculatorChanged.connect(self.undoRedoChanged) self.currentMinimizerChanged.connect(self.statusInfoChanged) #self.currentMinimizerChanged.connect(self.undoRedoChanged) self.currentMinimizerMethodChanged.connect(self.statusInfoChanged) #self.currentMinimizerMethodChanged.connect(self.undoRedoChanged) # Multithreading # self.stopFit.connect(self.lc.fitLogic.onStopFit) # start the undo/redo stack self.lc.initializeBorg() #################################################################################################################### #################################################################################################################### # Charts #################################################################################################################### #################################################################################################################### # 1d plotting @Property('QVariant', notify=dummySignal) def plotting1d(self): return self.lc.chartsLogic.plotting1d() # 3d plotting @Property('QVariant', notify=dummySignal) def plotting3dLibs(self): return self.lc.chartsLogic.plotting3dLibs() @Property('QVariant', notify=current3dPlottingLibChanged) def current3dPlottingLib(self): return self.lc.chartsLogic.current3dPlottingLib() @current3dPlottingLib.setter @property_stack_deco('Changing 3D library from {old_value} to {new_value}') def current3dPlottingLib(self, plotting_lib): self.lc.chartsLogic._current_3d_plotting_lib = plotting_lib self.current3dPlottingLibChanged.emit() def onCurrent3dPlottingLibChanged(self): self.lc.chartsLogic.onCurrent3dPlottingLibChanged() # Structure view @Property(bool, notify=structureViewChanged) def showBonds(self): return self.lc.chartsLogic.showBonds() @showBonds.setter def showBonds(self, show_bonds: bool): self.lc.chartsLogic.setShowBons(show_bonds) self.structureViewChanged.emit() @Property(float, notify=structureViewChanged) def bondsMaxDistance(self): return self.chartsLogic.bondsMaxDistance() @bondsMaxDistance.setter def bondsMaxDistance(self, max_distance: float): self.lc.chartsLogic.setBondsMaxDistance(max_distance) self.structureViewChanged.emit() #################################################################################################################### #################################################################################################################### # PROJECT #################################################################################################################### #################################################################################################################### @Property('QVariant', notify=projectInfoChanged) def projectInfoAsJson(self): return self.lc.state._project_info @projectInfoAsJson.setter def projectInfoAsJson(self, json_str): self.lc.state.projectInfoAsJson(json_str) self.projectInfoChanged.emit() @Property(str, notify=projectInfoChanged) def projectInfoAsCif(self): return self.lc.state.projectInfoAsCif() @Slot(str, str) def editProjectInfo(self, key, value): self.lc.state.editProjectInfo(key, value) self.projectInfoChanged.emit() @Property(str, notify=projectInfoChanged) def currentProjectPath(self): return self.lc.state._currentProjectPath @currentProjectPath.setter def currentProjectPath(self, new_path): self.lc.state.currentProjectPath(new_path) self.projectInfoChanged.emit() @Slot() def createProject(self): self.lc.state.createProject() @Property(bool, notify=stateChanged) def stateHasChanged(self): return self.lc.state._state_changed @stateHasChanged.setter def stateHasChanged(self, changed: bool): if self.lc.state._state_changed == changed: return self.lc.state.stateHasChanged(changed) self.stateChanged.emit(changed) # TODO: Move to State.py def _onStateChanged(self, changed=True): self.stateHasChanged = changed #################################################################################################################### # Phase models (list, xml, cif) #################################################################################################################### @Property('QVariant', notify=phasesAsObjChanged) def phasesAsObj(self): return self.lc.state._phases_as_obj @Property(str, notify=phasesAsXmlChanged) def phasesAsXml(self): return self.lc.state._phases_as_xml @Property(str, notify=phasesAsCifChanged) def phasesAsCif(self): return self.lc.state._phases_as_cif @phasesAsCif.setter @property_stack_deco def phasesAsCif(self, phases_as_cif): self.lc.state.phasesAsCif(phases_as_cif) self.lc.parametersChanged.emit() @Property(str, notify=phasesAsCifChanged) def phasesAsExtendedCif(self): return self.lc.state.phasesAsExtendedCif() def _setPhasesAsObj(self): start_time = timeit.default_timer() self.lc.state._setPhasesAsObj() print("+ _setPhasesAsObj: {0:.3f} s".format(timeit.default_timer() - start_time)) self.phasesAsObjChanged.emit() def _setPhasesAsXml(self): start_time = timeit.default_timer() self.lc.state._setPhasesAsXml() print("+ _setPhasesAsXml: {0:.3f} s".format(timeit.default_timer() - start_time)) self.phasesAsXmlChanged.emit() def _setPhasesAsCif(self): start_time = timeit.default_timer() self.lc.state._setPhasesAsCif() print("+ _setPhasesAsCif: {0:.3f} s".format(timeit.default_timer() - start_time)) self.phasesAsCifChanged.emit() def _onStructureParametersChanged(self): print("***** _onStructureParametersChanged") self._setPhasesAsObj() # 0.025 s self._setPhasesAsXml() # 0.065 s self._setPhasesAsCif() # 0.010 s self.stateChanged.emit(True) #################################################################################################################### # Phase: Add / Remove #################################################################################################################### @Slot(str) def addSampleFromCif(self, cif_url): self.lc.state.addSampleFromCif(cif_url) self.lc.phaseAdded.emit() @Slot() def addDefaultPhase(self): print("+ addDefaultPhase") self.lc.state.addDefaultPhase() self.lc.phaseAdded.emit() @Slot(str) def removePhase(self, phase_name: str): self.lc.removePhase(phase_name) def _onPhaseAdded(self): print("***** _onPhaseAdded") self.lc._onPhaseAdded() self.phasesEnabled.emit() self.structureParametersChanged.emit() self.projectInfoChanged.emit() @Property(bool, notify=phasesEnabled) def samplesPresent(self) -> bool: return self.lc.samplesPresent() #################################################################################################################### # Phase: Symmetry #################################################################################################################### # Crystal system @Property('QVariant', notify=structureParametersChanged) def crystalSystemList(self): return self.lc.state.crystalSystemList() @Property(str, notify=structureParametersChanged) def currentCrystalSystem(self): return self.lc.state.currentCrystalSystem() @currentCrystalSystem.setter def currentCrystalSystem(self, new_system: str): self.lc.state.setCurrentCrystalSystem(new_system) self.structureParametersChanged.emit() @Property('QVariant', notify=structureParametersChanged) def formattedSpaceGroupList(self): return self.lc.state.formattedSpaceGroupList() @Property(int, notify=structureParametersChanged) def currentSpaceGroup(self): return self.lc.state.getCurrentSpaceGroup() @currentSpaceGroup.setter def currentSpaceGroup(self, new_idx: int): self.lc.state.currentSpaceGroup(new_idx) self.structureParametersChanged.emit() @Property('QVariant', notify=structureParametersChanged) def formattedSpaceGroupSettingList(self): return self.lc.state.formattedSpaceGroupSettingList() @Property(int, notify=structureParametersChanged) def currentSpaceGroupSetting(self): return self.lc.state.currentSpaceGroupSetting() @currentSpaceGroupSetting.setter def currentSpaceGroupSetting(self, new_number: int): self.lc.state.setCurrentSpaceGroupSetting(new_number) self.structureParametersChanged.emit() #################################################################################################################### # Phase: Atoms #################################################################################################################### @Slot() def addDefaultAtom(self): try: self.lc.state.addDefaultAtom() self.structureParametersChanged.emit() except AttributeError: print("Error: failed to add atom") @Slot(str) def removeAtom(self, atom_label: str): self.lc.state.removeAtom(atom_label) self.structureParametersChanged.emit() #################################################################################################################### # Current phase #################################################################################################################### @Property(int, notify=currentPhaseChanged) def currentPhaseIndex(self): return self.lc.state._current_phase_index @currentPhaseIndex.setter def currentPhaseIndex(self, new_index: int): if self.lc.state.currentPhaseIndex(new_index): self.currentPhaseChanged.emit() def _onCurrentPhaseChanged(self): print("***** _onCurrentPhaseChanged") self.structureViewChanged.emit() @Slot(str) def setCurrentPhaseName(self, name): self.lc.state.setCurrentPhaseName(name) self.lc.parametersChanged.emit() self.projectInfoChanged.emit() @Property('QVariant', notify=experimentDataChanged) def experimentDataAsObj(self): return self.lc.state.experimentDataAsObj() @Slot(str) def setCurrentExperimentDatasetName(self, name): self.lc.state.setCurrentExperimentDatasetName(name) self.experimentDataChanged.emit() self.projectInfoChanged.emit() #################################################################################################################### #################################################################################################################### # EXPERIMENT #################################################################################################################### #################################################################################################################### @Property(str, notify=experimentDataAsXmlChanged) def experimentDataAsXml(self): return self.lc.state._experiment_data_as_xml def _setExperimentDataAsXml(self): print("+ _setExperimentDataAsXml") self.lc.state._setExperimentDataAsXml() self.experimentDataAsXmlChanged.emit() def _onExperimentDataChanged(self): print("***** _onExperimentDataChanged") self._setExperimentDataAsXml() self.stateChanged.emit(True) #################################################################################################################### # Experiment data: Add / Remove #################################################################################################################### @Slot(str) def addExperimentDataFromXye(self, file_url): self.lc.state.addExperimentDataFromXye(file_url) self._onExperimentDataAdded() self.experimentLoadedChanged.emit() @Slot() def removeExperiment(self): print("+ removeExperiment") self.lc.state.removeExperiment() self._onExperimentDataRemoved() self.experimentLoadedChanged.emit() def _loadExperimentData(self, file_url): print("+ _loadExperimentData") return self.lc.state._loadExperimentData(file_url) def _onExperimentDataAdded(self): print("***** _onExperimentDataAdded") self.lc._onExperimentDataAdded() def _onExperimentDataRemoved(self): print("***** _onExperimentDataRemoved") self.lc.chartsLogic._plotting_1d_proxy.clearFrontendState() self.experimentDataChanged.emit() #################################################################################################################### # Experiment loaded and skipped flags #################################################################################################################### @Property(bool, notify=experimentLoadedChanged) def experimentLoaded(self): return self.lc.state._experiment_loaded @experimentLoaded.setter def experimentLoaded(self, loaded: bool): self.lc.state.experimentLoaded(loaded) self.experimentLoadedChanged.emit() @Property(bool, notify=experimentSkippedChanged) def experimentSkipped(self): return self.lc.state._experiment_skipped @experimentSkipped.setter def experimentSkipped(self, skipped: bool): self.lc.state.experimentSkipped(skipped) self.experimentSkippedChanged.emit() def _onExperimentLoadedChanged(self): print("***** _onExperimentLoadedChanged") if self.experimentLoaded: self._onParametersChanged() self._onInstrumentParametersChanged() self._onPatternParametersChanged() def _onExperimentSkippedChanged(self): print("***** _onExperimentSkippedChanged") if self.experimentSkipped: self._onParametersChanged() self._onInstrumentParametersChanged() self._onPatternParametersChanged() self.lc.state._updateCalculatedData() #################################################################################################################### # Simulation parameters #################################################################################################################### @Property('QVariant', notify=simulationParametersChanged) def simulationParametersAsObj(self): return self.lc.state._simulation_parameters_as_obj @simulationParametersAsObj.setter def simulationParametersAsObj(self, json_str): self.lc.state.simulationParametersAsObj(json_str) self.simulationParametersChanged.emit() def _onSimulationParametersChanged(self): print("***** _onSimulationParametersChanged") self.lc.state._updateCalculatedData() #################################################################################################################### # Pattern parameters (scale, zero_shift, backgrounds) #################################################################################################################### @Property('QVariant', notify=patternParametersAsObjChanged) def patternParametersAsObj(self): return self.lc.state._pattern_parameters_as_obj def _setPatternParametersAsObj(self): start_time = timeit.default_timer() self.lc.state._setPatternParametersAsObj() print("+ _setPatternParametersAsObj: {0:.3f} s".format( timeit.default_timer() - start_time)) self.patternParametersAsObjChanged.emit() def _onPatternParametersChanged(self): print("***** _onPatternParametersChanged") self._setPatternParametersAsObj() #################################################################################################################### # Instrument parameters (wavelength, resolution_u, ..., resolution_y) #################################################################################################################### @Property('QVariant', notify=instrumentParametersAsObjChanged) def instrumentParametersAsObj(self): return self.lc.state._instrument_parameters_as_obj @Property(str, notify=instrumentParametersAsXmlChanged) def instrumentParametersAsXml(self): return self.lc.state._instrument_parameters_as_xml def _setInstrumentParametersAsObj(self): start_time = timeit.default_timer() self.lc.state._setInstrumentParametersAsObj() print("+ _setInstrumentParametersAsObj: {0:.3f} s".format( timeit.default_timer() - start_time)) self.instrumentParametersAsObjChanged.emit() def _setInstrumentParametersAsXml(self): start_time = timeit.default_timer() self.lc.state._setInstrumentParametersAsXml() print("+ _setInstrumentParametersAsXml: {0:.3f} s".format( timeit.default_timer() - start_time)) self.instrumentParametersAsXmlChanged.emit() def _onInstrumentParametersChanged(self): print("***** _onInstrumentParametersChanged") self._setInstrumentParametersAsObj() self._setInstrumentParametersAsXml() #################################################################################################################### # Background #################################################################################################################### @Property('QVariant', notify=dummySignal) def backgroundProxy(self): return self.lc._background_proxy #################################################################################################################### #################################################################################################################### # ANALYSIS #################################################################################################################### #################################################################################################################### #################################################################################################################### # Fitables (parameters table from analysis tab & ...) #################################################################################################################### @Property('QVariant', notify=parametersAsObjChanged) def parametersAsObj(self): return self.lc.state._parameters_as_obj @Property(str, notify=parametersAsXmlChanged) def parametersAsXml(self): return self.lc.state._parameters_as_xml def _setParametersAsObj(self): start_time = timeit.default_timer() self.lc.state._setParametersAsObj() print( "+ _setParametersAsObj: {0:.3f} s".format(timeit.default_timer() - start_time)) self.parametersAsObjChanged.emit() def _setParametersAsXml(self): start_time = timeit.default_timer() self.lc.state._setParametersAsXml() print( "+ _setParametersAsXml: {0:.3f} s".format(timeit.default_timer() - start_time)) self.parametersAsXmlChanged.emit() def _onParametersChanged(self): print("***** _onParametersChanged") self._setParametersAsObj() self._setParametersAsXml() self.stateChanged.emit(True) # Filtering @Slot(str) def setParametersFilterCriteria(self, new_criteria): self.lc.state.setParametersFilterCriteria(new_criteria) self._onParametersChanged() #################################################################################################################### # Any parameter #################################################################################################################### @Slot(str, 'QVariant') def editParameter(self, obj_id: str, new_value: Union[bool, float, str]): self.lc.state.editParameter(obj_id, new_value) #################################################################################################################### # Minimizer #################################################################################################################### @Property('QVariant', notify=dummySignal) def minimizerNames(self): return self.lc.fitLogic.fitter.available_engines @Property(int, notify=currentMinimizerChanged) def currentMinimizerIndex(self): return self.lc.fitLogic.currentMinimizerIndex() @currentMinimizerIndex.setter @property_stack_deco('Minimizer change') def currentMinimizerIndex(self, new_index: int): self.lc.fitLogic.setCurrentMinimizerIndex(new_index) def _onCurrentMinimizerChanged(self): print("***** _onCurrentMinimizerChanged") self.lc.fitLogic.onCurrentMinimizerChanged() # Minimizer method @Property('QVariant', notify=currentMinimizerChanged) def minimizerMethodNames(self): return self.lc.fitLogic.minimizerMethodNames() @Property(int, notify=currentMinimizerMethodChanged) def currentMinimizerMethodIndex(self): return self.lc.fitLogic._current_minimizer_method_index @currentMinimizerMethodIndex.setter @property_stack_deco('Minimizer method change') def currentMinimizerMethodIndex(self, new_index: int): self.lc.currentMinimizerMethodIndex(new_index) def _onCurrentMinimizerMethodChanged(self): print("***** _onCurrentMinimizerMethodChanged") #################################################################################################################### # Calculator #################################################################################################################### @Property('QVariant', notify=dummySignal) def calculatorNames(self): names = [ f'{name} (experimental)' if name in ['CrysFML', 'GSASII'] else name for name in self.lc._interface.available_interfaces ] return names @Property(int, notify=currentCalculatorChanged) def currentCalculatorIndex(self): return self.lc.currentCalculatorIndex() @currentCalculatorIndex.setter @property_stack_deco('Calculation engine change') def currentCalculatorIndex(self, new_index: int): if self.lc.setCurrentCalculatorIndex(new_index): print("***** _onCurrentCalculatorChanged") self.lc.state._onCurrentCalculatorChanged() self.lc.state._updateCalculatedData() self.currentCalculatorChanged.emit() # TODO: Move to State.py #################################################################################################################### # Fitting #################################################################################################################### @Slot() def fit(self): # Currently using python threads from the `threading` module, # since QThreads don't seem to properly work under macos self.lc.fitLogic.fit(self.lc.state._data) @Property('QVariant', notify=fitResultsChanged) def fitResults(self): return self.lc.fitLogic._fit_results @Property(bool, notify=fitFinishedNotify) def isFitFinished(self): return self.lc.fitLogic._fit_finished #################################################################################################################### #################################################################################################################### # Report #################################################################################################################### #################################################################################################################### @Slot(str) def setReport(self, report): """ Keep the QML generated HTML report for saving """ self.lc.state.setReport(report) @Slot(str) def saveReport(self, filepath): """ Save the generated report to the specified file Currently only html """ success = self.lc.state.saveReport(filepath) self.htmlExportingFinished.emit(success, filepath) #################################################################################################################### #################################################################################################################### # STATUS #################################################################################################################### #################################################################################################################### @Property('QVariant', notify=statusInfoChanged) def statusModelAsObj(self): return self.lc.statusModelAsObj() @Property(str, notify=statusInfoChanged) def statusModelAsXml(self): return self.lc.statusModelAsXml() def _onStatusInfoChanged(self): pass #################################################################################################################### #################################################################################################################### # Project examples #################################################################################################################### #################################################################################################################### @Property(str, notify=dummySignal) def projectExamplesAsXml(self): return self.lc.state.projectExamplesAsXml() #################################################################################################################### #################################################################################################################### # Screen recorder #################################################################################################################### #################################################################################################################### @Property('QVariant', notify=dummySignal) def screenRecorder(self): return self.lc._screen_recorder #################################################################################################################### #################################################################################################################### # State save/load #################################################################################################################### #################################################################################################################### @Slot() def saveProject(self): self.lc.state.saveProject() self.stateChanged.emit(False) @Slot(str) def loadProjectAs(self, filepath): self.lc.state._loadProjectAs(filepath) self.stateChanged.emit(False) @Slot() def loadProject(self): self.lc.state._loadProject() self.lc._background_proxy.onAsObjChanged() self.stateChanged.emit(False) @Slot(str) def loadExampleProject(self, filepath): self.lc.state._loadProjectAs(filepath) self.lc._background_proxy.onAsObjChanged() self.currentProjectPath = '--- EXAMPLE ---' self.stateChanged.emit(False) @Property(str, notify=dummySignal) def projectFilePath(self): return self.lc.state.project_save_filepath @Property(bool, notify=projectCreatedChanged) def projectCreated(self): return self.lc.state._project_created @projectCreated.setter def projectCreated(self, created: bool): if self.lc.state.setProjectCreated(created): self.projectCreatedChanged.emit() @Slot() def resetState(self): self.lc.state.resetState() self.lc._background_proxy.removeAllPoints() self.lc.chartsLogic._plotting_1d_proxy.clearBackendState() self.lc.chartsLogic._plotting_1d_proxy.clearFrontendState() self.resetUndoRedoStack() self.experimentDataChanged.emit() self.stateChanged.emit(False) #################################################################################################################### # Undo/Redo stack operations #################################################################################################################### @Property(bool, notify=undoRedoChanged) def canUndo(self) -> bool: return self.lc.stackLogic.canUndo() @Property(bool, notify=undoRedoChanged) def canRedo(self) -> bool: return self.lc.stackLogic.canRedo() @Slot() def undo(self): self.lc.stackLogic.undo() @Slot() def redo(self): self.lc.stackLogic.redo() @Property(str, notify=undoRedoChanged) def undoText(self): return self.lc.stackLogic.undoText() @Property(str, notify=undoRedoChanged) def redoText(self): return self.lc.stackLogic.redoText() @Slot() def resetUndoRedoStack(self): self.lc.stackLogic.resetUndoRedoStack() self.undoRedoChanged.emit()
class CommandWidget(TabWidgetExtension, QWidget): """Output for running queue""" # log state __log = False insertTextSignal = Signal(str, dict) updateCommandSignal = Signal(str) cliButtonsSateSignal = Signal(bool) cliValidateSignal = Signal(bool) resetSignal = Signal() def __init__(self, parent=None, proxyModel=None, controlQueue=None, log=None): super(CommandWidget, self).__init__(parent=parent, tabWidgetChild=self) self.__log = log self.__output = None self.__rename = None self.__tab = None # self.oCommand = MKVCommand() self.algorithm = None self.oCommand = MKVCommandParser() self.controlQueue = controlQueue self.parent = parent self.proxyModel = proxyModel self.model = proxyModel.sourceModel() self.outputWindow = QOutputTextWidget(self) self.log = log self._initControls() self._initUI() self._initHelper() def _initControls(self): # # command line # self.frmCmdLine = QFormLayout() btnPasteClipboard = QPushButtonWidget( Text.txt0164, function=lambda: qtRunFunctionInThread(self.pasteClipboard), margins=" ", toolTip=Text.txt0165, ) self.cmdLine = QLineEdit() self.cmdLine.setValidator( ValidateCommand(self, self.cliValidateSignal, log=self.log) ) self.frmCmdLine.addRow(btnPasteClipboard, self.cmdLine) self.frmCmdLine.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow) self.command = QWidget() self.command.setLayout(self.frmCmdLine) # # Button group definition # self.btnGroup = QGroupBox() self.btnGrid = QGridLayout() btnAddCommand = QPushButtonWidget( Text.txt0160, function=lambda: self.addCommand(JobStatus.Waiting), margins=" ", toolTip=Text.txt0161, ) btnRename = QPushButtonWidget( Text.txt0182, function=self.parent.renameWidget.setAsCurrentTab, margins=" ", toolTip=Text.txt0183, ) btnAddQueue = QPushButtonWidget( Text.txt0166, function=lambda: self.addCommand(JobStatus.AddToQueue), margins=" ", toolTip=Text.txt0167, ) btnStartQueue = QPushButtonWidget( Text.txt0126, function=self.parent.jobsQueue.run, margins=" ", toolTip=Text.txt0169, ) btnAnalysis = QPushButtonWidget( Text.txt0170, function=lambda: qtRunFunctionInThread( runAnalysis, command=self.cmdLine.text(), output=self.output, log=self.log, ), margins=" ", toolTip=Text.txt0171, ) btnShowCommands = QPushButtonWidget( Text.txt0172, function=lambda: qtRunFunctionInThread( showCommands, output=self.output, command=self.cmdLine.text(), oCommand=self.oCommand, log=self.log, ), margins=" ", toolTip=Text.txt0173, ) btnCheckFiles = QPushButtonWidget( Text.txt0174, function=lambda: qtRunFunctionInThread( checkFiles, output=self.output, command=self.cmdLine.text(), oCommand=self.oCommand, log=self.log, ), margins=" ", toolTip=Text.txt0175, ) btnClear = QPushButtonWidget( Text.txt0176, function=self.clearOutputWindow, margins=" ", toolTip=Text.txt0177, ) btnReset = QPushButtonWidget( Text.txt0178, function=self.reset, margins=" ", toolTip=Text.txt0179, ) self.btnGrid.addWidget(btnAddCommand, 0, 0) self.btnGrid.addWidget(btnRename, 0, 1) self.btnGrid.addWidget(btnAddQueue, 1, 0) self.btnGrid.addWidget(btnStartQueue, 1, 1) self.btnGrid.addWidget(HorizontalLine(), 2, 0, 1, 2) self.btnGrid.addWidget(btnAnalysis, 3, 0) self.btnGrid.addWidget(btnShowCommands, 3, 1) self.btnGrid.addWidget(btnCheckFiles, 4, 0) self.btnGrid.addWidget(HorizontalLine(), 5, 0, 1, 2) self.btnGrid.addWidget(btnClear, 6, 0) self.btnGrid.addWidget(btnReset, 6, 1) self.btnGroup.setLayout(self.btnGrid) self.btnGroupBox = QGroupBox() self.btnHBox = QHBoxLayout() self.lblAlgorithm = QLabelWidget( Text.txt0094, textSuffix=": ", ) self.rbZero = QRadioButton("0", self) self.rbOne = QRadioButton("1", self) self.rbTwo = QRadioButton("2", self) btnDefaultAlgorithm = QPushButtonWidget( Text.txt0092, function=self.setDefaultAlgorithm, margins=" ", toolTip=Text.txt0093, ) self.radioButtons = [self.rbZero, self.rbOne, self.rbTwo] self.btnHBox.addWidget(self.lblAlgorithm) self.btnHBox.addWidget(self.rbZero) self.btnHBox.addWidget(self.rbOne) self.btnHBox.addWidget(self.rbTwo) self.btnHBox.addWidget(btnDefaultAlgorithm) self.btnGroupBox.setLayout(self.btnHBox) def _initUI(self): grid = QGridLayout() grid.addWidget(self.command, 0, 0, 1, 2) grid.addWidget(self.btnGroupBox, 1, 0) grid.addWidget(self.btnGroup, 2, 0) grid.addWidget(self.outputWindow, 2, 1, 10, 1) self.setLayout(grid) def _initHelper(self): # # Signal interconnections # # local button state connect to related state self.parent.jobsQueue.addQueueItemSignal.connect( lambda: self.jobStartQueueState(True) ) self.parent.jobsQueue.queueEmptiedSignal.connect( lambda: self.jobStartQueueState(False) ) # job related self.parent.jobsQueue.runJobs.startSignal.connect(lambda: self.jobStatus(True)) self.parent.jobsQueue.runJobs.finishedSignal.connect( lambda: self.jobStatus(False) ) # map insertText signal to outputWidget one self.insertText = self.outputWindow.insertTextSignal # command self.updateCommandSignal.connect(self.updateCommand) self.cliButtonsSateSignal.connect(self.cliButtonsState) self.cliValidateSignal.connect(self.cliValidate) # # button state # # Command related # self.frmCmdLine.itemAt(0, QFormLayout.LabelRole).widget().setEnabled(False) self.cliButtonsState(False) self.btnGrid.itemAt(_Button.ANALYSIS).widget().setEnabled(False) # Clear buttons related self.btnGrid.itemAt(_Button.CLEAR).widget().setEnabled(False) self.btnGrid.itemAt(_Button.RESET).widget().setEnabled(False) # connect text windows textChanged to clearButtonState function self.outputWindow.textChanged.connect(self.clearButtonState) # connect command line textChanged to analysisButtonState function self.cmdLine.textChanged.connect(self.analysisButtonState) # Job Queue related self.btnGrid.itemAt(_Button.STARTQUEUE).widget().setEnabled(False) # Job Added to Queue self.parent.jobsQueue.addQueueItemSignal.connect(self.printJobIDAdded) # # Misc # self.cmdLine.setClearButtonEnabled(True) # button at end of line to clear it # Algorithm radio buttons self.rbZero.toggled.connect(lambda: self.toggledRadioButton(self.rbZero)) self.rbOne.toggled.connect(lambda: self.toggledRadioButton(self.rbOne)) self.rbTwo.toggled.connect(lambda: self.toggledRadioButton(self.rbTwo)) self.setDefaultAlgorithm() @classmethod def classLog(cls, setLogging=None): """ get/set logging at class level every class instance will log unless overwritten Args: setLogging (bool): - True class will log - False turn off logging - None returns current Value Returns: bool: returns the current value set """ if setLogging is not None: if isinstance(setLogging, bool): cls.__log = setLogging return cls.__log @property def log(self): """ class property can be used to override the class global logging setting Returns: bool: True if logging is enable False otherwise """ if self.__log is not None: return self.__log return CommandWidget.classLog() @log.setter def log(self, value): """set instance log variable""" if isinstance(value, bool) or value is None: self.__log = value # No variable used so for now use class log ValidateCommand.classLog(value) self.outputWindow.log = value @property def output(self): return self.__output @output.setter def output(self, value): self.__output = value @property def rename(self): return self.__rename @rename.setter def rename(self, value): if isinstance(value, object): self.__rename = value @Slot(list) def applyRename(self, renameFiles): if self.oCommand: self.oCommand.renameOutputFiles(renameFiles) @Slot(bool) def cliButtonsState(self, validateOK): """ cliButtonsState change enabled status for buttons related with command line Args: validateOK (bool): True to enable, False to disable """ for b in [ _Button.ADDCOMMAND, _Button.RENAME, _Button.ADDQUEUE, _Button.SHOWCOMMANDS, _Button.CHECKFILES, ]: button = self.btnGrid.itemAt(b).widget() button.setEnabled(validateOK) @Slot(bool) def cliValidate(self, validateOK): """ cliValidate Slot used by ValidateCommnad Args: validateOK (bool): True if command line is Ok. False otherwise. """ if validateOK: self.output.command.emit( "Command looks ok.\n", {LineOutput.AppendEnd: True} ) else: if self.cmdLine.text() != "": self.output.command.emit("Bad command.\n", {LineOutput.AppendEnd: True}) self.cliButtonsState(validateOK) self.updateObjCommnad(validateOK) @Slot(bool) def jobStartQueueState(self, state): if state and not isThreadRunning(config.WORKERTHREADNAME): self.btnGrid.itemAt(_Button.STARTQUEUE).widget().setEnabled(True) else: self.btnGrid.itemAt(_Button.STARTQUEUE).widget().setEnabled(False) @Slot(bool) def updateObjCommnad(self, valid): """Update the command object""" if valid: self.oCommand.command = self.cmdLine.text() if self.rename is not None: self.rename.setFilesSignal.emit(self.oCommand) self.rename.applyFileRenameSignal.connect(self.applyRename) else: self.oCommand.command = "" if self.rename is not None: self.rename.clear() @Slot(str) def updateCommand(self, command): """Update command input widget""" self.cmdLine.clear() self.cmdLine.setText(command) self.cmdLine.setCursorPosition(0) @Slot(int) def updateAlgorithm(self, algorithm): if 0 <= algorithm < len(self.radioButtons): self.radioButtons[algorithm].setChecked(True) @Slot(bool) def jobStatus(self, running): """ jobStatus receive Signals for job start/end Args: running (bool): True if job started. False if ended. """ if running: self.jobStartQueueState(False) palette = QPalette() color = checkColor( QColor(42, 130, 218), config.data.get(config.ConfigKey.DarkMode) ) palette.setColor(QPalette.WindowText, color) self.parent.jobsLabel.setPalette(palette) else: palette = QPalette() color = checkColor(None, config.data.get(config.ConfigKey.DarkMode)) palette.setColor(QPalette.WindowText, color) self.parent.jobsLabel.setPalette(palette) def addCommand(self, status): """ addCommand add command row in jobs table Args: status (JobStatus): Status for job to be added should be either JobStatus.Waiting or JobStatus.AddToQueue """ totalJobs = self.model.rowCount() command = self.cmdLine.text() # [cell value, tooltip, obj] data = [ ["", "", self.algorithm], [status, "Status code", None], [command, command, self.oCommand], ] self.model.insertRows(totalJobs, 1, data=data) self.cmdLine.clear() def analysisButtonState(self): """Set clear button state""" if self.cmdLine.text() != "": self.btnGrid.itemAt(_Button.ANALYSIS).widget().setEnabled(True) else: self.btnGrid.itemAt(_Button.ANALYSIS).widget().setEnabled(False) def clearButtonState(self): """Set clear button state""" if self.outputWindow.toPlainText() != "": self.btnGrid.itemAt(_Button.CLEAR).widget().setEnabled(True) else: self.btnGrid.itemAt(_Button.CLEAR).widget().setEnabled(False) def clearOutputWindow(self): """ clearOutputWindow clear the command output window """ language = config.data.get(config.ConfigKey.Language) bAnswer = False # Clear output window? title = "Clear output" msg = "¿" if language == "es" else "" msg += "Clear output window" + "?" bAnswer = yesNoDialog(self, msg, title) if bAnswer: self.outputWindow.clear() def printJobIDAdded(self, index): jobID = self.model.dataset[index.row(), index.column()] self.output.command.emit( f"Job: {jobID} added to Queue...\n", {LineOutput.AppendEnd: True} ) def pasteClipboard(self): """Paste clipboard to command QLineEdit""" clip = QApplication.clipboard().text() if clip: self.output.command.emit( "Checking command...\n", {LineOutput.AppendEnd: True} ) self.update() self.updateCommandSignal.emit(clip) def reset(self): """ reset program status """ language = config.data.get(config.ConfigKey.Language) if not isThreadRunning(config.WORKERTHREADNAME): language = config.data.get(config.ConfigKey.Language) bAnswer = False # Clear output window? title = "Reset" msg = "¿" if language == "es" else "" msg += "Reset Application" + "?" bAnswer = yesNoDialog(self, msg, title) if bAnswer: self.cmdLine.clear() self.outputWindow.clear() self.output.jobOutput.clear() self.output.errorOutput.clear() self.resetSignal.emit() else: messageBox(self, "Reset", "Jobs are running..") def resetButtonState(self): """Set clear button state""" if self.output.jobOutput.toPlainText() != "": self.btnGrid.itemAt(_Button.RESET).widget().setEnabled(True) else: self.btnGrid.itemAt(_Button.RESET).widget().setEnabled(False) def setDefaultAlgorithm(self): # # Algorithm # if config.data.get(config.ConfigKey.Algorithm) is not None: currentAlgorithm = config.data.get(config.ConfigKey.Algorithm) self.radioButtons[currentAlgorithm].setChecked(True) def setLanguage(self): """ setLanguage language use in buttons/labels to be called by MainWindow """ for index in range(self.frmCmdLine.rowCount()): widget = self.frmCmdLine.itemAt(index, QFormLayout.LabelRole).widget() if isinstance(widget, QPushButtonWidget): widget.setLanguage() # widget.setText(" " + _(widget.originalText) + " ") # widget.setToolTip(_(widget.toolTip)) for index in range(self.btnHBox.count()): widget = self.btnHBox.itemAt(index).widget() if isinstance( widget, ( QLabelWidget, QPushButtonWidget, ), ): widget.setLanguage() for index in range(self.btnGrid.count()): widget = self.btnGrid.itemAt(index).widget() if isinstance(widget, QPushButtonWidget): widget.setLanguage() # widget.setText(" " + _(widget.originalText) + " ") # widget.setToolTip(_(widget.toolTip)) def toggledRadioButton(self, rButton): for index, rb in enumerate(self.radioButtons): if rb.isChecked(): self.algorithm = index
class ImportParamWidget(QWidget): """Widget for allowing user to select video parameters. Args: file_path: file path/name import_type: data about the parameters for this type of video Note: Object is a widget with the UI for params specific to this video type. """ changed = Signal() def __init__(self, file_path: str, import_type: dict, *args, **kwargs): super(ImportParamWidget, self).__init__(*args, **kwargs) self.file_path = file_path self.import_type = import_type self.widget_elements = {} self.video_params = {} option_layout = self.make_layout() # self.changed.connect( lambda: print(self.get_values()) ) self.setLayout(option_layout) def make_layout(self) -> QLayout: """Builds the layout of widgets for user-selected import parameters.""" param_list = self.import_type["params"] widget_layout = QVBoxLayout() widget_elements = dict() for param_item in param_list: name = param_item["name"] type = param_item["type"] options = param_item.get("options", None) if type == "radio": radio_group = QButtonGroup(parent=self) option_list = options.split(",") selected_option = option_list[0] for option in option_list: btn_widget = QRadioButton(option) if option == selected_option: btn_widget.setChecked(True) widget_layout.addWidget(btn_widget) radio_group.addButton(btn_widget) radio_group.buttonToggled.connect(lambda: self.changed.emit()) widget_elements[name] = radio_group elif type == "check": check_widget = QCheckBox(name) check_widget.stateChanged.connect(lambda: self.changed.emit()) widget_layout.addWidget(check_widget) widget_elements[name] = check_widget elif type == "function_menu": list_widget = QComboBox() # options has name of method which returns list of options option_list = getattr(self, options)() for option in option_list: list_widget.addItem(option) list_widget.currentIndexChanged.connect( lambda: self.changed.emit()) widget_layout.addWidget(list_widget) widget_elements[name] = list_widget self.widget_elements = widget_elements return widget_layout def get_values(self, only_required=False): """Method to get current user-selected values for import parameters. Args: only_required: Only return the parameters that are required for instantiating `Video` object Returns: Dict of param keys/values. Note: It's easiest if the return dict matches the arguments we need for the Video object, so we'll add the file name to the dict even though it's not a user-selectable param. """ param_list = self.import_type["params"] param_values = {} param_values["filename"] = self.file_path for param_item in param_list: name = param_item["name"] type = param_item["type"] is_required = param_item.get("required", False) if not only_required or is_required: value = None if type == "radio": value = self.widget_elements[name].checkedButton().text() elif type == "check": value = self.widget_elements[name].isChecked() elif type == "function_menu": value = self.widget_elements[name].currentText() param_values[name] = value return param_values def set_values_from_video(self, video): """Set the form fields using attributes on video.""" param_list = self.import_type["params"] for param in param_list: name = param["name"] type = param["type"] if hasattr(video, name): val = getattr(video, name) widget = self.widget_elements[name] if hasattr(widget, "isChecked"): widget.setChecked(val) elif hasattr(widget, "value"): widget.setValue(val) elif hasattr(widget, "currentText"): widget.setCurrentText(str(val)) elif hasattr(widget, "text"): widget.setText(str(val)) def _get_h5_dataset_options(self) -> list: """Method to get a list of all datasets in hdf5 file. Args: None. Returns: List of datasets in the hdf5 file for our import item. Note: This is used to populate the "function_menu"-type param. """ try: with h5py.File(self.file_path, "r") as f: options = self._find_h5_datasets("", f) except Exception as e: options = [] return options def _find_h5_datasets(self, data_path, data_object) -> list: """Recursively find datasets in hdf5 file.""" options = [] for key in data_object.keys(): if isinstance(data_object[key], h5py._hl.dataset.Dataset): if len(data_object[key].shape) == 4: options.append(data_path + "/" + key) elif isinstance(data_object[key], h5py._hl.group.Group): options.extend( self._find_h5_datasets(data_path + "/" + key, data_object[key])) return options def boundingRect(self) -> QRectF: """Method required by Qt.""" return QRectF() def paint(self, painter, option, widget=None): """Method required by Qt.""" pass