Ejemplo n.º 1
0
class PlotSignal(QObject):
    plot = Signal()
Ejemplo n.º 2
0
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)
Ejemplo n.º 3
0
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)
Ejemplo n.º 4
0
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
Ejemplo n.º 5
0
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")
Ejemplo n.º 6
0
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
Ejemplo n.º 7
0
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())))
Ejemplo n.º 8
0
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)
Ejemplo n.º 9
0
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()
Ejemplo n.º 10
0
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
Ejemplo n.º 11
0
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
Ejemplo n.º 12
0
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
Ejemplo n.º 13
0
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'
Ejemplo n.º 14
0
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)
Ejemplo n.º 15
0
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()
Ejemplo n.º 16
0
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")
Ejemplo n.º 17
0
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
Ejemplo n.º 19
0
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()
Ejemplo n.º 20
0
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")
Ejemplo n.º 21
0
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
Ejemplo n.º 22
0
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
Ejemplo n.º 23
0
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()
Ejemplo n.º 24
0
class CourseTreeWidgetSignals(QObject):
    clearFileListWidget = Signal()
    appendRowFileListWidget = Signal(QStandardItem)
    addDownloadTask = Signal(str, str, str)
Ejemplo n.º 25
0
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()
Ejemplo n.º 26
0
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
Ejemplo n.º 27
0
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
Ejemplo n.º 28
0
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()
Ejemplo n.º 29
0
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
Ejemplo n.º 30
0
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