コード例 #1
0
class Env(QWidget):

    makePlot = Signal(int, object, object, name='makePlot')

    def __init__(self, comm, plot_spawner, parent=None):
        super(__class__, self).__init__(parent)
        self.setWindowTitle("Env")
        self.comm = comm

        self.srcBox = QLineEdit(self)
        self.srcSelect = QPushButton('Select', self)
        self.srcFilter = QPushButton('Filter', self)
        self.postName = QLabel('Entry name', self)
        self.postBox = QLineEdit(self)
        self.tabs = QTabWidget(self)
        self.tabs.addTab(TimePlot(0, self.comm, plot_spawner, self.tabs),
                         "Mean v Time")
        self.tabs.addTab(ScanPlot(1, self.comm, plot_spawner, self.tabs),
                         "Mean v Scan")
        self.button = QPushButton('Plot', self)
        self.button.clicked.connect(self.on_click)

        self.src = QGroupBox("Source Channel")
        self.src_layout = QHBoxLayout(self.src)
        self.src_layout.addWidget(self.srcBox)
        self.src_layout.addWidget(self.srcSelect)
        self.src_layout.addWidget(self.srcFilter)

        self.post = QHBoxLayout()
        self.post.addWidget(self.postName)
        self.post.addWidget(self.postBox)

        self.plot = QGroupBox("Plot Type")
        self.plot_layout = QVBoxLayout(self.plot)
        self.plot_layout.addWidget(self.tabs)

        self.env_layout = QVBoxLayout(self)
        self.env_layout.addWidget(self.src)
        self.env_layout.addLayout(self.post)
        self.env_layout.addWidget(self.plot)
        self.env_layout.addWidget(self.button)

        for i in range(self.tabs.count()):
            self.makePlot.connect(self.tabs.widget(i).request_plot)

    @Slot()
    def on_click(self):
        src = self.srcBox.text()
        post = self.postBox.text()

        if src:
            self.makePlot.emit(self.tabs.currentIndex(), src, post)
コード例 #2
0
class DetailedProgressWidget(QWidget):
    def __init__(self, parent, state_colors):
        super(DetailedProgressWidget, self).__init__(parent)
        self.setWindowTitle("Realization Progress")
        layout = QGridLayout(self)

        self.iterations = QTabWidget()
        self.state_colors = state_colors

        self.single_view = SingleTableView()
        self.single_view.setModel(
            SingleProgressModel(self.single_view, state_colors))
        self.single_view_label = QLabel("Realization details")

        layout.addWidget(self.iterations, 1, 0)
        layout.addWidget(self.single_view_label, 2, 0)
        layout.addWidget(self.single_view, 3, 0)

        self.setLayout(layout)

        self.layout().setRowStretch(1, 1)
        self.layout().setRowStretch(3, 1)
        self.progress = None
        self.selected_realization = -1
        self.current_iteration = -1
        self.resize(parent.width(), parent.height())
        self.progress = {}

    def set_progress(self, progress, iteration):
        self.progress = progress
        for i in progress:  #create all detailed views if they havent been constructed yet
            if self.iterations.widget(i):
                continue
            detailed_progress_widget = DetailedProgress(
                self.state_colors, self)
            detailed_progress_widget.clicked.connect(self.show_selection)
            detailed_progress_widget.set_progress(progress[i], i)
            detailed_progress_widget.show()
            self.iterations.addTab(detailed_progress_widget,
                                   "Realizations for iteration {}".format(i))

        current_progress_widget = self.iterations.widget(iteration)
        current_progress_widget.set_progress(progress[iteration], iteration)

        self.update_single_view()
        self.update()

    def show_selection(self, iens):
        if not self.progress:
            return

        self.current_iteration = self.iterations.currentIndex()

        for i in range(0, self.iterations.count()):
            if i == self.current_iteration:
                continue
            self.iterations.widget(i).selected_realization = -1

        self.selected_realization = iens
        self.single_view_label.setText(
            "Realization id: {} in iteration {}".format(
                iens, self.current_iteration))
        self.update_single_view()

    def update_single_view(self):
        if not self.single_view.isVisible():
            return
        if not self.current_iteration in self.progress:
            return
        if not self.selected_realization in self.progress[
                self.current_iteration]:
            return

        self.single_view.update_data(
            self.progress[self.current_iteration][self.selected_realization]
            [0], self.current_iteration, self.selected_realization)
コード例 #3
0
ファイル: importwizard.py プロジェクト: zhoufan766/spyder
class ImportWizard(BaseDialog):
    """Text data import wizard"""
    def __init__(self,
                 parent,
                 text,
                 title=None,
                 icon=None,
                 contents_title=None,
                 varname=None):
        QDialog.__init__(self, parent)

        # Destroying the C++ object right after closing the dialog box,
        # otherwise it may be garbage-collected in another QThread
        # (e.g. the editor's analysis thread in Spyder), thus leading to
        # a segmentation fault on UNIX or an application crash on Windows
        self.setAttribute(Qt.WA_DeleteOnClose)

        if title is None:
            title = _("Import wizard")
        self.setWindowTitle(title)
        if icon is None:
            self.setWindowIcon(ima.icon('fileimport'))
        if contents_title is None:
            contents_title = _("Raw text")

        if varname is None:
            varname = _("variable_name")

        self.var_name, self.clip_data = None, None

        # Setting GUI
        self.tab_widget = QTabWidget(self)
        self.text_widget = ContentsWidget(self, text)
        self.table_widget = PreviewWidget(self)

        self.tab_widget.addTab(self.text_widget, _("text"))
        self.tab_widget.setTabText(0, contents_title)
        self.tab_widget.addTab(self.table_widget, _("table"))
        self.tab_widget.setTabText(1, _("Preview"))
        self.tab_widget.setTabEnabled(1, False)

        name_layout = QHBoxLayout()
        name_label = QLabel(_("Variable Name"))
        name_layout.addWidget(name_label)

        self.name_edt = QLineEdit()
        self.name_edt.setText(varname)
        name_layout.addWidget(self.name_edt)

        btns_layout = QHBoxLayout()
        cancel_btn = QPushButton(_("Cancel"))
        btns_layout.addWidget(cancel_btn)
        cancel_btn.clicked.connect(self.reject)
        h_spacer = QSpacerItem(40, 20, QSizePolicy.Expanding,
                               QSizePolicy.Minimum)
        btns_layout.addItem(h_spacer)
        self.back_btn = QPushButton(_("Previous"))
        self.back_btn.setEnabled(False)
        btns_layout.addWidget(self.back_btn)
        self.back_btn.clicked.connect(ft_partial(self._set_step, step=-1))
        self.fwd_btn = QPushButton(_("Next"))
        if not text:
            self.fwd_btn.setEnabled(False)
        btns_layout.addWidget(self.fwd_btn)
        self.fwd_btn.clicked.connect(ft_partial(self._set_step, step=1))
        self.done_btn = QPushButton(_("Done"))
        self.done_btn.setEnabled(False)
        btns_layout.addWidget(self.done_btn)
        self.done_btn.clicked.connect(self.process)

        self.text_widget.asDataChanged.connect(self.fwd_btn.setEnabled)
        self.text_widget.asDataChanged.connect(self.done_btn.setDisabled)
        layout = QVBoxLayout()
        layout.addLayout(name_layout)
        layout.addWidget(self.tab_widget)
        layout.addLayout(btns_layout)
        self.setLayout(layout)

    def _focus_tab(self, tab_idx):
        """Change tab focus"""
        for i in range(self.tab_widget.count()):
            self.tab_widget.setTabEnabled(i, False)
        self.tab_widget.setTabEnabled(tab_idx, True)
        self.tab_widget.setCurrentIndex(tab_idx)

    def _set_step(self, step):
        """Proceed to a given step"""
        new_tab = self.tab_widget.currentIndex() + step
        assert new_tab < self.tab_widget.count() and new_tab >= 0
        if new_tab == self.tab_widget.count() - 1:
            try:
                self.table_widget.open_data(
                    self._get_plain_text(), self.text_widget.get_col_sep(),
                    self.text_widget.get_row_sep(),
                    self.text_widget.trnsp_box.isChecked(),
                    self.text_widget.get_skiprows(),
                    self.text_widget.get_comments())
                self.done_btn.setEnabled(True)
                self.done_btn.setDefault(True)
                self.fwd_btn.setEnabled(False)
                self.back_btn.setEnabled(True)
            except (SyntaxError, AssertionError) as error:
                QMessageBox.critical(
                    self, _("Import wizard"),
                    _("<b>Unable to proceed to next step</b>"
                      "<br><br>Please check your entries."
                      "<br><br>Error message:<br>%s") % str(error))
                return
        elif new_tab == 0:
            self.done_btn.setEnabled(False)
            self.fwd_btn.setEnabled(True)
            self.back_btn.setEnabled(False)
        self._focus_tab(new_tab)

    def get_data(self):
        """Return processed data"""
        # It is import to avoid accessing Qt C++ object as it has probably
        # already been destroyed, due to the Qt.WA_DeleteOnClose attribute
        return self.var_name, self.clip_data

    def _simplify_shape(self, alist, rec=0):
        """Reduce the alist dimension if needed"""
        if rec != 0:
            if len(alist) == 1:
                return alist[-1]
            return alist
        if len(alist) == 1:
            return self._simplify_shape(alist[-1], 1)
        return [self._simplify_shape(al, 1) for al in alist]

    def _get_table_data(self):
        """Return clipboard processed as data"""
        data = self._simplify_shape(self.table_widget.get_data())
        if self.table_widget.array_btn.isChecked():
            return array(data)
        elif pd and self.table_widget.df_btn.isChecked():
            info = self.table_widget.pd_info
            buf = io.StringIO(self.table_widget.pd_text)
            return pd.read_csv(buf, **info)
        return data

    def _get_plain_text(self):
        """Return clipboard as text"""
        return self.text_widget.text_editor.toPlainText()

    @Slot()
    def process(self):
        """Process the data from clipboard"""
        var_name = self.name_edt.text()
        try:
            self.var_name = str(var_name)
        except UnicodeEncodeError:
            self.var_name = to_text_string(var_name)
        if self.text_widget.get_as_data():
            self.clip_data = self._get_table_data()
        elif self.text_widget.get_as_code():
            self.clip_data = try_to_eval(to_text_string(
                self._get_plain_text()))
        else:
            self.clip_data = to_text_string(self._get_plain_text())
        self.accept()
コード例 #4
0
ファイル: gh_login.py プロジェクト: impact27/spyder
class DlgGitHubLogin(QDialog):
    """Dialog to submit error reports to Github."""

    def __init__(self, parent, username, password, token, remember=False,
                 remember_token=False):
        QDialog.__init__(self, parent)

        title = _("Sign in to Github")
        self.resize(415, 375)
        self.setWindowTitle(title)
        self.setWindowFlags(
            self.windowFlags() & ~Qt.WindowContextHelpButtonHint)

        # Header
        html = ('<html><head/><body><p align="center">'
                '{title}</p></body></html>')
        lbl_html = QLabel(html.format(title=title))
        lbl_html.setStyleSheet('font-size: 16px;')

        # Tabs
        self.tabs = QTabWidget()

        # Basic form layout
        basic_form_layout = QFormLayout()
        basic_form_layout.setContentsMargins(-1, 0, -1, -1)

        basic_lbl_msg = QLabel(_("For regular users, i.e. users <b>without</b>"
                                 " two-factor authentication enabled"))
        basic_lbl_msg.setWordWrap(True)
        basic_lbl_msg.setAlignment(Qt.AlignJustify)

        lbl_user = QLabel(_("Username:"******"", QWidget())

        lbl_password = QLabel(_("Password: "******"Remember me"))
            self.cb_remember.setToolTip(_("Spyder will save your credentials "
                                          "safely"))
            self.cb_remember.setChecked(remember)
            basic_form_layout.setWidget(4, QFormLayout.FieldRole,
                                        self.cb_remember)

        # Basic auth tab
        basic_auth = QWidget()
        basic_layout = QVBoxLayout()
        basic_layout.addSpacerItem(QSpacerItem(QSpacerItem(0, 8)))
        basic_layout.addWidget(basic_lbl_msg)
        basic_layout.addSpacerItem(
            QSpacerItem(QSpacerItem(0, 50, vPolicy=QSizePolicy.Expanding)))
        basic_layout.addLayout(basic_form_layout)
        basic_layout.addSpacerItem(
            QSpacerItem(QSpacerItem(0, 50, vPolicy=QSizePolicy.Expanding)))
        basic_auth.setLayout(basic_layout)
        self.tabs.addTab(basic_auth, _("Password Only"))

        # Token form layout
        token_form_layout = QFormLayout()
        token_form_layout.setContentsMargins(-1, 0, -1, -1)

        token_lbl_msg = QLabel(_("For users <b>with</b> two-factor "
                                 "authentication enabled, or who prefer a "
                                 "per-app token authentication.<br><br>"
                                 "You can go <b><a href=\"{}\">here</a></b> "
                                 "and click \"Generate token\" at the bottom "
                                 "to create a new token to use for this, with "
                                 "the appropriate permissions.").format(
                                                                    TOKEN_URL))
        token_lbl_msg.setOpenExternalLinks(True)
        token_lbl_msg.setWordWrap(True)
        token_lbl_msg.setAlignment(Qt.AlignJustify)

        lbl_token = QLabel("Token: ")
        token_form_layout.setWidget(1, QFormLayout.LabelRole, lbl_token)
        self.le_token = QLineEdit()
        self.le_token.setEchoMode(QLineEdit.Password)
        self.le_token.textChanged.connect(self.update_btn_state)
        token_form_layout.setWidget(1, QFormLayout.FieldRole, self.le_token)

        self.cb_remember_token = None
        # Same validation as with cb_remember
        if self.is_keyring_available() and valid_py_os:
            self.cb_remember_token = QCheckBox(_("Remember token"))
            self.cb_remember_token.setToolTip(_("Spyder will save your "
                                                "token safely"))
            self.cb_remember_token.setChecked(remember_token)
            token_form_layout.setWidget(3, QFormLayout.FieldRole,
                                        self.cb_remember_token)

        # Token auth tab
        token_auth = QWidget()
        token_layout = QVBoxLayout()
        token_layout.addSpacerItem(QSpacerItem(QSpacerItem(0, 8)))
        token_layout.addWidget(token_lbl_msg)
        token_layout.addSpacerItem(
            QSpacerItem(QSpacerItem(0, 50, vPolicy=QSizePolicy.Expanding)))
        token_layout.addLayout(token_form_layout)
        token_layout.addSpacerItem(
            QSpacerItem(QSpacerItem(0, 50, vPolicy=QSizePolicy.Expanding)))
        token_auth.setLayout(token_layout)
        self.tabs.addTab(token_auth, _("Access Token"))

        # Sign in button
        self.bt_sign_in = QPushButton(_("Sign in"))
        self.bt_sign_in.clicked.connect(self.accept)
        self.bt_sign_in.setDisabled(True)

        # Main layout
        layout = QVBoxLayout()
        layout.addWidget(lbl_html)
        layout.addWidget(self.tabs)
        layout.addWidget(self.bt_sign_in)
        self.setLayout(layout)

        # Final adjustments
        if username and password:
            self.le_user.setText(username)
            self.le_password.setText(password)
            self.bt_sign_in.setFocus()
        elif username:
            self.le_user.setText(username)
            self.le_password.setFocus()
        elif token:
            self.le_token.setText(token)
        else:
            self.le_user.setFocus()

        self.setFixedSize(self.width(), self.height())
        self.le_password.installEventFilter(self)
        self.le_user.installEventFilter(self)
        self.tabs.currentChanged.connect(self.update_btn_state)

    def eventFilter(self, obj, event):
        interesting_objects = [self.le_password, self.le_user]
        if obj in interesting_objects and event.type() == QEvent.KeyPress:
            if (event.key() == Qt.Key_Return and
                    event.modifiers() & Qt.ControlModifier and
                    self.bt_sign_in.isEnabled()):
                self.accept()
                return True
        return False

    def update_btn_state(self):
        user = to_text_string(self.le_user.text()).strip() != ''
        password = to_text_string(self.le_password.text()).strip() != ''
        token = to_text_string(self.le_token.text()).strip() != ''
        enable = ((user and password and
                  self.tabs.currentIndex() == 0) or
                  (token and self.tabs.currentIndex() == 1))
        self.bt_sign_in.setEnabled(enable)

    def is_keyring_available(self):
        """Check if keyring is available for password storage."""
        try:
            import keyring  # analysis:ignore
            return True
        except Exception:
            return False

    @classmethod
    def login(cls, parent, username, password, token,
              remember, remember_token):
        dlg = DlgGitHubLogin(parent, username, password, token, remember,
                             remember_token)
        if dlg.exec_() == dlg.Accepted:
            user = dlg.le_user.text()
            password = dlg.le_password.text()
            token = dlg.le_token.text()
            if dlg.cb_remember:
                remember = dlg.cb_remember.isChecked()
            else:
                remember = False
            if dlg.cb_remember_token:
                remember_token = dlg.cb_remember_token.isChecked()
            else:
                remember_token = False

            credentials = dict(username=user,
                               password=password,
                               token=token,
                               remember=remember,
                               remember_token=remember_token)
            return credentials

        return dict(username=None,
                    password=None,
                    token=None,
                    remember=False,
                    remember_token=False)
コード例 #5
0
class DlgGitHubLogin(QDialog):
    """Dialog to submit error reports to Github."""
    def __init__(self,
                 parent,
                 username,
                 password,
                 token,
                 remember=False,
                 remember_token=False):
        QDialog.__init__(self, parent)

        title = _("Sign in to Github")
        self.resize(415, 375)
        self.setWindowTitle(title)
        self.setWindowFlags(self.windowFlags()
                            & ~Qt.WindowContextHelpButtonHint)

        # Header
        html = ('<html><head/><body><p align="center">'
                '{title}</p></body></html>')
        lbl_html = QLabel(html.format(title=title))
        lbl_html.setStyleSheet('font-size: 16px;')

        # Tabs
        self.tabs = QTabWidget()

        # Basic form layout
        basic_form_layout = QFormLayout()
        basic_form_layout.setContentsMargins(-1, 0, -1, -1)

        basic_lbl_msg = QLabel(
            _("For regular users, i.e. users <b>without</b>"
              " two-factor authentication enabled"))
        basic_lbl_msg.setWordWrap(True)
        basic_lbl_msg.setAlignment(Qt.AlignJustify)

        lbl_user = QLabel(_("Username:"******"", QWidget())

        lbl_password = QLabel(_("Password: "******"Remember me"))
            self.cb_remember.setToolTip(
                _("Spyder will save your credentials "
                  "safely"))
            self.cb_remember.setChecked(remember)
            basic_form_layout.setWidget(4, QFormLayout.FieldRole,
                                        self.cb_remember)

        # Basic auth tab
        basic_auth = QWidget()
        basic_layout = QVBoxLayout()
        basic_layout.addSpacerItem(QSpacerItem(QSpacerItem(0, 8)))
        basic_layout.addWidget(basic_lbl_msg)
        basic_layout.addSpacerItem(
            QSpacerItem(QSpacerItem(0, 50, vPolicy=QSizePolicy.Expanding)))
        basic_layout.addLayout(basic_form_layout)
        basic_layout.addSpacerItem(
            QSpacerItem(QSpacerItem(0, 50, vPolicy=QSizePolicy.Expanding)))
        basic_auth.setLayout(basic_layout)
        self.tabs.addTab(basic_auth, _("Password Only"))

        # Token form layout
        token_form_layout = QFormLayout()
        token_form_layout.setContentsMargins(-1, 0, -1, -1)

        token_lbl_msg = QLabel(
            _("For users <b>with</b> two-factor "
              "authentication enabled, or who prefer a "
              "per-app token authentication.<br><br>"
              "You can go <b><a href=\"{}\">here</a></b> "
              "and click \"Generate token\" at the bottom "
              "to create a new token to use for this, with "
              "the appropriate permissions.").format(TOKEN_URL))
        token_lbl_msg.setOpenExternalLinks(True)
        token_lbl_msg.setWordWrap(True)
        token_lbl_msg.setAlignment(Qt.AlignJustify)

        lbl_token = QLabel("Token: ")
        token_form_layout.setWidget(1, QFormLayout.LabelRole, lbl_token)
        self.le_token = QLineEdit()
        self.le_token.setEchoMode(QLineEdit.Password)
        self.le_token.textChanged.connect(self.update_btn_state)
        token_form_layout.setWidget(1, QFormLayout.FieldRole, self.le_token)

        self.cb_remember_token = None
        # Same validation as with cb_remember
        if self.is_keyring_available() and valid_py_os:
            self.cb_remember_token = QCheckBox(_("Remember token"))
            self.cb_remember_token.setToolTip(
                _("Spyder will save your "
                  "token safely"))
            self.cb_remember_token.setChecked(remember_token)
            token_form_layout.setWidget(3, QFormLayout.FieldRole,
                                        self.cb_remember_token)

        # Token auth tab
        token_auth = QWidget()
        token_layout = QVBoxLayout()
        token_layout.addSpacerItem(QSpacerItem(QSpacerItem(0, 8)))
        token_layout.addWidget(token_lbl_msg)
        token_layout.addSpacerItem(
            QSpacerItem(QSpacerItem(0, 50, vPolicy=QSizePolicy.Expanding)))
        token_layout.addLayout(token_form_layout)
        token_layout.addSpacerItem(
            QSpacerItem(QSpacerItem(0, 50, vPolicy=QSizePolicy.Expanding)))
        token_auth.setLayout(token_layout)
        self.tabs.addTab(token_auth, _("Access Token"))

        # Sign in button
        self.bt_sign_in = QPushButton(_("Sign in"))
        self.bt_sign_in.clicked.connect(self.accept)
        self.bt_sign_in.setDisabled(True)

        # Main layout
        layout = QVBoxLayout()
        layout.addWidget(lbl_html)
        layout.addWidget(self.tabs)
        layout.addWidget(self.bt_sign_in)
        self.setLayout(layout)

        # Final adjustments
        if username and password:
            self.le_user.setText(username)
            self.le_password.setText(password)
            self.bt_sign_in.setFocus()
        elif username:
            self.le_user.setText(username)
            self.le_password.setFocus()
        elif token:
            self.le_token.setText(token)
        else:
            self.le_user.setFocus()

        self.setFixedSize(self.width(), self.height())
        self.le_password.installEventFilter(self)
        self.le_user.installEventFilter(self)
        self.tabs.currentChanged.connect(self.update_btn_state)

    def eventFilter(self, obj, event):
        interesting_objects = [self.le_password, self.le_user]
        if obj in interesting_objects and event.type() == QEvent.KeyPress:
            if (event.key() == Qt.Key_Return
                    and event.modifiers() & Qt.ControlModifier
                    and self.bt_sign_in.isEnabled()):
                self.accept()
                return True
        return False

    def update_btn_state(self):
        user = to_text_string(self.le_user.text()).strip() != ''
        password = to_text_string(self.le_password.text()).strip() != ''
        token = to_text_string(self.le_token.text()).strip() != ''
        enable = ((user and password and self.tabs.currentIndex() == 0)
                  or (token and self.tabs.currentIndex() == 1))
        self.bt_sign_in.setEnabled(enable)

    def is_keyring_available(self):
        """Check if keyring is available for password storage."""
        try:
            import keyring  # analysis:ignore
            return True
        except Exception:
            return False

    @classmethod
    def login(cls, parent, username, password, token, remember,
              remember_token):
        dlg = DlgGitHubLogin(parent, username, password, token, remember,
                             remember_token)
        if dlg.exec_() == dlg.Accepted:
            user = dlg.le_user.text()
            password = dlg.le_password.text()
            token = dlg.le_token.text()
            if dlg.cb_remember:
                remember = dlg.cb_remember.isChecked()
            else:
                remember = False
            if dlg.cb_remember_token:
                remember_token = dlg.cb_remember_token.isChecked()
            else:
                remember_token = False

            credentials = dict(username=user,
                               password=password,
                               token=token,
                               remember=remember,
                               remember_token=remember_token)
            return credentials

        return dict(username=None,
                    password=None,
                    token=None,
                    remember=False,
                    remember_token=False)
コード例 #6
0
class ConfigurationManager(SiriusMainWindow):
    """."""

    NAME_COL = None
    CONFIG_TYPE_COL = None

    def __init__(self, model, parent=None):
        """Constructor."""
        super().__init__(parent)
        self._model = model
        self._logger = logging.getLogger(__name__)
        self._logger.setLevel(logging.INFO)
        self._setup_ui()
        self.setWindowTitle("Configuration Manager")

    def _setup_ui(self):
        # self.setGeometry(0, 0, 1600, 900)
        self.main_widget = QFrame()
        self.main_widget.setObjectName('ServConf')
        self.setCentralWidget(self.main_widget)
        self.layout = QGridLayout()
        self.main_widget.setLayout(self.layout)

        # Basic widgets
        self.editor = QTableView()
        self.delete_button = QPushButton('Delete', self)
        self.delete_button.setObjectName('DeleteButton')
        self.rename_button = QPushButton('Rename', self)
        self.rename_button.setObjectName('RenameButton')
        self.d_editor = QTableView()
        self.retrieve_button = QPushButton('Retrieve', self)
        self.retrieve_button.setObjectName('RetrieveButton')
        self.tree = QTreeView(self)
        self.config_type = QComboBox(parent=self)
        self.config_type.setModel(
                ConfigTypeModel(self._model, self.config_type))

        # Tab widgets
        self.tab1 = QWidget()
        self.tab1.layout = QVBoxLayout(self.tab1)
        self.tab2 = QWidget()
        self.tab2.layout = QVBoxLayout(self.tab2)
        self.tab1.layout.addWidget(self.editor)
        hlay = QHBoxLayout()
        hlay.addWidget(self.rename_button)
        hlay.addWidget(self.delete_button)
        self.tab1.layout.addLayout(hlay)
        self.tab2.layout.addWidget(self.d_editor)
        self.tab2.layout.addWidget(self.retrieve_button)

        self.editor_tab = QTabWidget(self)
        self.editor_tab.addTab(self.tab1, 'Configurations')
        self.editor_tab.addTab(self.tab2, 'Discarded Configurations')
        self.config_viewer = QWidget(self)
        self.config_viewer.layout = QVBoxLayout(self.config_viewer)
        self.config_viewer.layout.addWidget(self.editor_tab)
        self.config_viewer.layout.addWidget(self.tree)

        # Header widget
        self.header = QFrame(self)
        self.header.setObjectName('Header')
        self.header.layout = QHBoxLayout(self.header)
        self.header.layout.addStretch()
        self.header.layout.addWidget(
            QLabel('Configuration Database Manager', self.header))
        self.header.layout.addStretch()

        # Sub header with database genral information
        self.sub_header = QFrame(self)
        self.sub_header.setObjectName('SubHeader')
        self.sub_header.layout = QVBoxLayout(self.sub_header)

        self.server_layout = QHBoxLayout()
        self.server_layout.addWidget(QLabel('<b>Server:</b>', self.sub_header))
        self.server_layout.addWidget(QLabel(self._model.url, self.sub_header))
        self.server_layout.addStretch()

        self.size_layout = QHBoxLayout()
        self.size_layout.addWidget(QLabel('<b>DB Size:</b>', self.sub_header))
        try:
            dbsize = self._model.get_dbsize()
            dbsize = '{:.2f} MB'.format(dbsize/(1024*1024))
        except ConfigDBException:
            dbsize = 'Failed to retrieve information'
        self.size_layout.addWidget(QLabel(dbsize, self.sub_header))
        self.size_layout.addStretch()

        self.sub_header.layout.addLayout(self.server_layout)
        self.sub_header.layout.addLayout(self.size_layout)

        # Query form
        self.query_form = QFrame()
        self.query_form.setObjectName("QueryForm")
        self.query_form.layout = QVBoxLayout()
        self.query_form.setLayout(self.query_form.layout)

        self.configs_layout = QGridLayout()
        self.configs_layout.addWidget(QLabel('Configurations:', self), 0, 0)
        self.nr_configs = QLabel(self)
        self.configs_layout.addWidget(self.nr_configs, 0, 1)
        self.configs_layout.addWidget(QLabel('Discarded:', self), 0, 2)
        self.nr_discarded = QLabel(self)
        self.configs_layout.addWidget(self.nr_discarded, 0, 3)

        self.query_form.layout.addWidget(self.config_type)
        self.query_form.layout.addLayout(self.configs_layout)

        # Main widget layout setup
        self.layout.addWidget(self.header, 0, 0, 1, 3)
        self.layout.addWidget(self.sub_header, 1, 0, 1, 2)
        self.layout.addWidget(self.query_form, 2, 0, 1, 2)
        self.layout.addWidget(self.config_viewer, 3, 0, 1, 2)
        self.layout.addWidget(self.tree, 1, 2, 4, 1)
        # self.layout.addWidget(self.delete_button, 4, 0, 1, 2)
        self.layout.setColumnStretch(0, 1)
        self.layout.setColumnStretch(1, 2)
        self.layout.setColumnStretch(2, 2)

        # Set table models and options
        self.editor_model = ConfigDbTableModel('notexist', self._model)
        self.d_editor_model = ConfigDbTableModel('notexist', self._model, True)
        self.editor.setModel(self.editor_model)
        self.editor.setSelectionBehavior(self.editor.SelectRows)
        self.editor.setSortingEnabled(True)
        self.editor.horizontalHeader().setResizeMode(QHeaderView.Stretch)
        self.d_editor.setModel(self.d_editor_model)
        self.d_editor.setSelectionBehavior(self.editor.SelectRows)
        self.d_editor.setSortingEnabled(True)
        self.d_editor.horizontalHeader().setResizeMode(QHeaderView.Stretch)
        self.d_editor.setSelectionMode(self.d_editor.SingleSelection)
        # Set tree model and options
        self.tree_model = JsonTreeModel(None, None, self._model)
        self.tree.setModel(self.tree_model)
        # Delete button
        self.delete_button.setEnabled(False)
        self.rename_button.setEnabled(True)
        self.retrieve_button.setEnabled(False)

        # Signals and slots

        # Tab
        self.editor_tab.currentChanged.connect(self._tab_changed)
        # Fill tables when configuration is selected
        self.config_type.currentTextChanged.connect(self._fill_table)
        # Fill tree when a configuration is selected
        self.editor.selectionModel().selectionChanged.connect(
            lambda x, y: self._fill_tree())
        self.d_editor.selectionModel().selectionChanged.connect(
            lambda x, y: self._fill_tree())
        # Connect database error to slot that show messages
        self.editor_model.connectionError.connect(self._database_error)
        self.d_editor_model.connectionError.connect(self._database_error)
        # Makes tree column extend automatically to show content
        self.tree.expanded.connect(
            lambda idx: self.tree.resizeColumnToContents(idx.column()))
        # Button action
        self.delete_button.pressed.connect(self._remove_configuration)
        self.rename_button.pressed.connect(self._rename_configuration)
        self.retrieve_button.pressed.connect(self._retrieve_configuration)
        # Set constants
        ConfigurationManager.NAME_COL = \
            self.editor_model.horizontalHeader.index('name')
        ConfigurationManager.CONFIG_TYPE_COL = \
            self.editor_model.horizontalHeader.index('config_type')

        self.editor.resizeColumnsToContents()
        self.d_editor.resizeColumnsToContents()

    @Slot(str)
    def _fill_table(self, config_type):
        """Fill table with configuration of `config_type`."""
        leng = len(self._model.find_configs(
            config_type=config_type, discarded=False))
        self.nr_configs.setText(str(leng))
        leng = len(self._model.find_configs(
            config_type=config_type, discarded=True))
        self.nr_discarded.setText(str(leng))

        self.editor_model.setupModelData(config_type)
        self.d_editor_model.setupModelData(config_type)
        self.editor.resizeColumnsToContents()
        self.d_editor.resizeColumnsToContents()
        self.editor_model.sort(2, Qt.DescendingOrder)
        self.d_editor_model.sort(2, Qt.DescendingOrder)

    @Slot()
    def _fill_tree(self):
        if self.editor_tab.currentIndex() == 0:
            configs = list()
            rows = self._get_selected_rows(self.editor)
            # Get selected rows
            for row in rows:
                # Get name and configuration type
                configs.append(self._type_name(row, self.editor_model))
            # Set tree data
            self.tree_model.setupModelData(configs)
            if len(configs) == 1:
                self.delete_button.setEnabled(True)
                self.delete_button.setText(
                    'Delete {} ({})'.format(configs[0][1], configs[0][0]))
                self.rename_button.setEnabled(True)
                self.rename_button.setText(
                    'Rename {} ({})'.format(configs[0][1], configs[0][0]))

            elif len(configs) > 1:
                self.rename_button.setEnabled(False)
                self.rename_button.setText('Rename')
                self.delete_button.setEnabled(True)
                self.delete_button.setText(
                    'Delete {} configurations'.format(len(configs)))
            else:
                self.rename_button.setEnabled(False)
                self.rename_button.setText('Rename')
                self.delete_button.setEnabled(False)
            self.delete_button.style().polish(self.delete_button)
            self.rename_button.style().polish(self.rename_button)
        else:
            try:
                row = self._get_selected_rows(self.d_editor).pop()
            except KeyError:
                self.retrieve_button.setEnabled(False)
                self.retrieve_button.style().polish(self.retrieve_button)
            else:
                config_type, name = self._type_name(row, self.d_editor_model)
                self.tree_model.setupModelData([(config_type, name)])
                self.retrieve_button.setEnabled(True)
                self.retrieve_button.style().polish(self.retrieve_button)
        # self.tree.resizeColumnsToContents()

    @Slot()
    def _remove_configuration(self):
        type = QMessageBox.Question
        title = 'Remove configuration?'
        buttons = QMessageBox.Ok | QMessageBox.Cancel

        # self.editor.selectRow(index.row())
        rows = list(self._get_selected_rows(self.editor))
        message = 'Remove configurations:\n'
        for row in rows:
            config_type = self.editor_model.createIndex(row, 0).data()
            name = self.editor_model.createIndex(row, 1).data()
            message += '- {} ({})\n'.format(name, config_type)

        msg = QMessageBox(type, title, message, buttons).exec_()
        if msg == QMessageBox.Ok:
            rows.sort(reverse=True)
            for row in rows:
                self.editor_model.removeRows(row)

        self.editor.selectionModel().clearSelection()
        self._fill_table(self.config_type.currentText())

    @Slot()
    def _rename_configuration(self):
        # self.editor.selectRow(index.row())
        rows = list(self._get_selected_rows(self.editor))
        if not rows:
            return
        config_type = self.editor_model.createIndex(rows[0], 0).data()
        name = self.editor_model.createIndex(rows[0], 1).data()

        wid = RenameConfigDialog(config_type, self)
        wid.setWindowTitle('Rename: {}'.format(name))
        wid.search_le.setText(name)
        newname, status = wid.exec_()
        if not newname or not status:
            return
        self._model.rename_config(
            name, newname, config_type=config_type)

        self.editor.selectionModel().clearSelection()
        self._fill_table(self.config_type.currentText())

    @Slot()
    def _retrieve_configuration(self):
        type = QMessageBox.Question
        title = 'Retrieve configuration?'
        buttons = QMessageBox.Ok | QMessageBox.Cancel

        try:
            row = self._get_selected_rows(self.d_editor).pop()
        except KeyError:
            pass
        else:
            config_type, name = self._type_name(row, self.d_editor_model)
            name = name[:-37]
            message = \
                'Retrieve configuration {} ({})?'.format(config_type, name)
            msg = QMessageBox(type, title, message, buttons).exec_()
            if msg == QMessageBox.Ok:
                try:
                    self.d_editor_model.removeRows(row)
                except TypeError:
                    self._database_error(
                        'Exception',
                        'Configuration no longer is in the correct format',
                        'retrieve configuration')

        self.editor.selectionModel().clearSelection()
        self._fill_table(self.config_type.currentText())

    @Slot(int)
    def _tab_changed(self, index):
        if index == 0:
            self.editor.selectionModel().clearSelection()
            self.delete_button.setText('Delete')
            self.delete_button.setEnabled(False)
            self.delete_button.style().polish(self.delete_button)
            self.rename_button.setText('Rename')
            self.rename_button.setEnabled(False)
            self.rename_button.style().polish(self.rename_button)
        else:
            self.d_editor.selectionModel().clearSelection()
            self.retrieve_button.setEnabled(False)
            self.retrieve_button.style().polish(self.retrieve_button)
        self.tree_model.setupModelData([])

    @Slot(int, str, str)
    def _database_error(self, code, message, operation):
        type = QMessageBox.Warning
        title = 'Something went wrong'
        msg = '{}: {}, while trying to {}'.format(code, message, operation)
        QMessageBox(type, title, msg).exec_()

    def _get_selected_rows(self, table):
        index_list = table.selectionModel().selectedIndexes()
        return {idx.row() for idx in index_list}

    def _type_name(self, row, model):
        # Return config_type and name given a row and a table model
        return (model.createIndex(row, self.CONFIG_TYPE_COL).data(),
                model.createIndex(row, self.NAME_COL).data())
コード例 #7
0
class MainWindow(QMainWindow):
    def __init__(self, log, app):
        self.log = log.getChild('Main')
        self.app = app
        super().__init__()

        self.dark_theme = CONFIG['dark_theme_default']
        self.single_tab_mode = CONFIG['single_tab_mode_default']

        self.loggers_by_name = {}  # name -> LoggerTab
        self.popped_out_loggers = {}

        self.server_running = False
        self.shutting_down = False

        self.setupUi()
        self.start_server()

    def setupUi(self):
        self.resize(800, 600)
        self.setWindowTitle('cutelog')

        self.loggerTabWidget = QTabWidget(self)
        self.loggerTabWidget.setTabsClosable(True)
        self.loggerTabWidget.setMovable(True)
        self.loggerTabWidget.setTabBarAutoHide(True)
        self.loggerTabWidget.currentChanged.connect(self.change_actions_state)
        self.setCentralWidget(self.loggerTabWidget)

        self.statusbar = QStatusBar(self)
        self.setStatusBar(self.statusbar)

        self.setup_menubar()
        self.setup_action_triggers()
        self.setup_shortcuts()

        self.loggerTabWidget.tabCloseRequested.connect(self.close_tab)

        self.reload_stylesheet()
        self.restore_geometry()

        self.show()

    def setup_menubar(self):
        self.menubar = QMenuBar(self)
        self.setMenuBar(self.menubar)

        # File menu
        self.menuFile = self.menubar.addMenu("File")
        self.actionLoadRecords = self.menuFile.addAction('Load records')
        self.actionSaveRecords = self.menuFile.addAction('Save records')
        self.menuFile.addSeparator()
        self.actionDarkTheme = self.menuFile.addAction('Dark theme')
        self.actionDarkTheme.setCheckable(True)
        self.actionDarkTheme.setChecked(self.dark_theme)
        self.actionSingleTab = self.menuFile.addAction('Single tab mode')
        self.actionSingleTab.setCheckable(True)
        self.actionSingleTab.setChecked(self.single_tab_mode)
        # self.actionReloadStyle = self.menuFile.addAction('Reload style')
        self.actionSettings = self.menuFile.addAction('Settings')
        self.menuFile.addSeparator()
        self.actionQuit = self.menuFile.addAction('Quit')

        # Tab menu
        self.menuTab = self.menubar.addMenu("Tab")
        self.actionCloseTab = self.menuTab.addAction('Close')
        self.actionPopOut = self.menuTab.addAction('Pop out')
        self.actionRenameTab = self.menuTab.addAction('Rename')
        self.menuTab.addSeparator()
        self.actionPopIn = self.menuTab.addAction('Pop in tabs...')
        self.actionMergeTabs = self.menuTab.addAction('Merge tabs...')
        self.menuTab.addSeparator()
        self.actionExtraMode = self.menuTab.addAction('Extra mode')
        self.actionExtraMode.setCheckable(True)

        # Server menu
        self.menuServer = self.menubar.addMenu("Server")
        self.actionRestartServer = self.menuServer.addAction('Restart server')
        self.actionStartStopServer = self.menuServer.addAction('Stop server')

        # Records menu
        self.menuRecords = self.menubar.addMenu("Records")
        self.actionTrimTabRecords = self.menuRecords.addAction('Trim records')
        self.actionSetMaxCapacity = self.menuRecords.addAction(
            'Set max capacity')

        # Help menu
        self.menuHelp = self.menubar.addMenu("Help")
        self.actionAbout = self.menuHelp.addAction("About cutelog")

        self.change_actions_state(
        )  # to disable all logger actions, since they don't function yet

    def setup_action_triggers(self):
        # File menu
        self.actionLoadRecords.triggered.connect(self.open_load_records_dialog)
        self.actionSaveRecords.triggered.connect(self.open_save_records_dialog)
        self.actionDarkTheme.toggled.connect(self.toggle_dark_theme)
        self.actionSingleTab.triggered.connect(self.set_single_tab_mode)
        # self.actionReloadStyle.triggered.connect(self.reload_stylesheet)
        self.actionSettings.triggered.connect(self.settings_dialog)
        self.actionQuit.triggered.connect(self.shutdown)

        # Tab meny
        self.actionCloseTab.triggered.connect(self.close_current_tab)
        self.actionPopOut.triggered.connect(self.pop_out_tab)
        self.actionRenameTab.triggered.connect(self.rename_tab_dialog)
        self.actionPopIn.triggered.connect(self.pop_in_tabs_dialog)
        self.actionMergeTabs.triggered.connect(self.merge_tabs_dialog)
        self.actionExtraMode.triggered.connect(self.toggle_extra_mode)

        # Server menu
        self.actionRestartServer.triggered.connect(self.restart_server)
        self.actionStartStopServer.triggered.connect(self.start_or_stop_server)

        # Records menu
        self.actionTrimTabRecords.triggered.connect(self.trim_records_dialog)
        self.actionSetMaxCapacity.triggered.connect(self.max_capacity_dialog)

        # About menu
        self.actionAbout.triggered.connect(self.about_dialog)

    def change_actions_state(self, index=None):
        logger, _ = self.current_logger_and_index()
        # if there are no loggers in tabs, these actions will be disabled:
        actions = [
            self.actionCloseTab, self.actionExtraMode, self.actionPopOut,
            self.actionRenameTab, self.actionPopIn, self.actionTrimTabRecords,
            self.actionSetMaxCapacity, self.actionSaveRecords
        ]

        if not logger:
            for action in actions:
                action.setDisabled(True)
            self.actionExtraMode.setChecked(False)
            if len(self.popped_out_loggers) > 0:
                self.actionPopIn.setDisabled(False)
        else:
            for action in actions:
                action.setDisabled(False)
            if len(self.loggers_by_name) == self.loggerTabWidget.count():
                self.actionPopIn.setDisabled(True)
            self.actionExtraMode.setChecked(logger.extra_mode)

        # if all loggers are popped in
        if len(self.popped_out_loggers) == 0:
            self.actionPopIn.setDisabled(True)

        if len(self.loggers_by_name) <= 1:
            self.actionMergeTabs.setDisabled(True)
        else:
            self.actionMergeTabs.setDisabled(False)

    def set_single_tab_mode(self, enabled):
        self.single_tab_mode = enabled

    def setup_shortcuts(self):
        self.actionQuit.setShortcut('Ctrl+Q')
        self.actionDarkTheme.setShortcut('Ctrl+S')
        # self.actionReloadStyle.setShortcut('Ctrl+R')
        # self.actionSettings.setShortcut('Ctrl+A')
        self.actionCloseTab.setShortcut('Ctrl+W')

    def save_geometry(self):
        CONFIG.save_geometry(self.geometry())

    def restore_geometry(self):
        geometry = CONFIG.load_geometry()
        if geometry:
            self.resize(geometry.width(), geometry.height())

    def settings_dialog(self):
        d = SettingsDialog(self)
        d.setWindowModality(Qt.ApplicationModal)
        d.setAttribute(Qt.WA_DeleteOnClose, True)
        d.open()

    def about_dialog(self):
        d = AboutDialog(self)
        d.open()

    def reload_stylesheet(self):
        if self.dark_theme:
            self.reload_dark_style()
        else:
            self.reload_light_style()

    def reload_light_style(self):
        if CONFIG['light_theme_is_native']:
            self.set_style_to_stock()
            return
        f = QFile(":/light_theme.qss")
        f.open(QFile.ReadOnly | QFile.Text)
        ts = QTextStream(f)
        qss = ts.readAll()
        # f = open(Config.get_resource_path('light_theme.qss', 'resources/ui'), 'r')
        # qss = f.read()
        self.app.setStyleSheet(qss)

    def reload_dark_style(self):
        f = QFile(":/dark_theme.qss")
        f.open(QFile.ReadOnly | QFile.Text)
        ts = QTextStream(f)
        qss = ts.readAll()
        # f = open(Config.get_resource_path('dark_theme.qss', 'resources/ui'), 'r')
        # qss = f.read()
        self.app.setStyleSheet(qss)

    def set_style_to_stock(self):
        self.app.setStyleSheet('')

    def toggle_dark_theme(self, enabled):
        self.dark_theme = enabled
        self.reload_stylesheet()

        for logger in self.loggers_by_name.values():
            logger.set_dark_theme(enabled)

    def toggle_extra_mode(self, enabled):
        logger, _ = self.current_logger_and_index()
        if not logger:
            return
        logger.set_extra_mode(enabled)

    def start_server(self):
        self.log.debug('Starting the server')
        self.server = LogServer(self, self.on_connection, self.log)
        self.server.start()
        self.server_running = True
        self.actionStartStopServer.setText('Stop server')

    def stop_server(self):
        if self.server_running:
            self.log.debug('Stopping the server')
            self.server.close_server()
            self.server_running = False
            del self.server
            self.server = None
        self.actionStartStopServer.setText('Start server')

    def restart_server(self):
        self.log.debug('Restarting the server')
        self.stop_server()
        self.start_server()

    def start_or_stop_server(self):
        if self.server_running:
            self.stop_server()
        else:
            self.start_server()

    def on_connection(self, conn, conn_id):
        self.log.debug('New connection id={}'.format(conn_id))

        if self.single_tab_mode and len(self.loggers_by_name) > 0:
            new_logger = list(self.loggers_by_name.values())[0]
            new_logger.add_connection(conn)
        else:
            new_logger, index = self.create_logger(conn)
            self.loggerTabWidget.setCurrentIndex(index)

        conn.new_record.connect(new_logger.on_record)
        conn.connection_finished.connect(new_logger.remove_connection)

        if self.server.benchmark and conn_id == -1:
            from .listener import BenchmarkMonitor
            bm = BenchmarkMonitor(self, new_logger)
            bm.speed_readout.connect(self.set_status)
            conn.connection_finished.connect(bm.requestInterruption)
            self.server.threads.append(bm)
            bm.start()

    def create_logger(self, conn, name=None):
        name = self.make_logger_name_unique("Logger" if name is None else name)
        new_logger = LoggerTab(self.loggerTabWidget, name, conn, self.log,
                               self)
        new_logger.set_dark_theme(self.dark_theme)
        self.loggers_by_name[name] = new_logger
        index = self.loggerTabWidget.addTab(new_logger, name)
        return new_logger, index

    def make_logger_name_unique(self, name):
        name_f = "{} {{}}".format(name)
        c = 1
        while name in self.loggers_by_name:
            name = name_f.format(c)
            c += 1
        return name

    def set_status(self, string, timeout=3000):
        self.statusBar().showMessage(string, timeout)

    def rename_tab_dialog(self):
        logger, index = self.current_logger_and_index()
        if not logger:
            return

        d = QInputDialog(self)
        d.setLabelText('Enter the new name for the "{}" tab:'.format(
            logger.name))
        d.setWindowTitle('Rename the "{}" tab'.format(logger.name))
        d.textValueSelected.connect(self.rename_current_tab)
        d.open()

    def rename_current_tab(self, new_name):
        logger, index = self.current_logger_and_index()
        if new_name in self.loggers_by_name and new_name != logger.name:
            show_warning_dialog(
                self, "Rename error",
                'Logger named "{}" already exists.'.format(new_name))
            return
        self.log.debug('Renaming logger "{}" to "{}"'.format(
            logger.name, new_name))
        del self.loggers_by_name[logger.name]
        logger.name = new_name
        self.loggers_by_name[new_name] = logger
        logger.log.name = '.'.join(
            logger.log.name.split('.')[:-1]) + '.{}'.format(new_name)
        self.loggerTabWidget.setTabText(index, new_name)

    def trim_records_dialog(self):
        logger, index = self.current_logger_and_index()
        if not logger:
            return

        d = QInputDialog(self)
        d.setInputMode(QInputDialog.IntInput)
        d.setIntRange(
            0, 100000000)  # because it sets intMaximum to 99 by default. why??
        d.setLabelText('Keep this many records out of {}:'.format(
            logger.record_model.rowCount()))
        d.setWindowTitle('Trim tab records of "{}" logger'.format(logger.name))
        d.intValueSelected.connect(self.trim_current_tab_records)
        d.open()

    def trim_current_tab_records(self, n):
        logger, index = self.current_logger_and_index()
        logger.record_model.trim_except_last_n(n)

    def max_capacity_dialog(self):
        logger, index = self.current_logger_and_index()
        if not logger:
            return

        d = QInputDialog(self)
        d.setInputMode(QInputDialog.IntInput)
        d.setIntRange(
            0, 100000000)  # because it sets intMaximum to 99 by default. why??
        max_now = logger.record_model.max_capacity
        max_now = "not set" if max_now is None else max_now
        label_str = 'Set max capacity for "{}" logger\nCurrently {}. Set to 0 to disable:'
        d.setLabelText(label_str.format(logger.name, max_now))
        d.setWindowTitle('Set max capacity')
        d.intValueSelected.connect(self.set_max_capacity)
        d.open()

    def set_max_capacity(self, n):
        logger, index = self.current_logger_and_index()
        logger.set_max_capacity(n)

    def merge_tabs_dialog(self):
        d = MergeDialog(self, self.loggers_by_name)
        d.setWindowModality(Qt.WindowModal)
        d.merge_tabs_signal.connect(self.merge_tabs)
        d.show()

    def merge_tabs(self, dst, srcs, keep_alive):
        self.log.debug('Merging tabs: dst="{}", srcs={}, keep={}'.format(
            dst, srcs, keep_alive))

        dst_logger = self.loggers_by_name[dst]
        for src_name in srcs:
            src_logger = self.loggers_by_name[src_name]

            dst_logger.merge_with_records(src_logger.record_model.records)

            if keep_alive:
                for conn in src_logger.connections:
                    conn.new_record.disconnect(src_logger.on_record)
                    conn.connection_finished.disconnect(
                        src_logger.remove_connection)
                    conn.new_record.connect(dst_logger.on_record)
                    dst_logger.add_connection(conn)
                src_logger.connections.clear()
            self.destroy_logger(src_logger)

    def close_current_tab(self):
        _, index = self.current_logger_and_index()
        if index is None:
            return
        self.close_tab(index)

    def close_tab(self, index):
        self.log.debug("Tab close requested: {}".format(index))
        logger = self.loggerTabWidget.widget(index)
        self.loggerTabWidget.removeTab(index)
        self.log.debug(logger.name)
        self.destroy_logger(logger)

    def destroy_logger(self, logger):
        del self.loggers_by_name[logger.name]
        logger.setParent(None)
        logger.destroy()
        del logger

    def close_popped_out_logger(self, logger):
        del self.loggers_by_name[logger.name]
        del self.popped_out_loggers[logger.name]
        del logger
        if len(self.popped_out_loggers):
            self.actionPopIn.setDisabled(True)

    def current_logger_and_index(self):
        index = self.loggerTabWidget.currentIndex()
        if index == -1:
            return None, None

        logger = self.loggerTabWidget.widget(index)
        return logger, index

    def pop_out_tab(self):
        logger, index = self.current_logger_and_index()
        if not logger:
            return
        self.log.debug("Tab pop out requested: {}".format(int(index)))

        logger.destroyed.connect(logger.closeEvent)
        logger.setAttribute(Qt.WA_DeleteOnClose, True)
        logger.setWindowFlags(Qt.Window)
        logger.setWindowTitle('cutelog: "{}"'.format(
            self.loggerTabWidget.tabText(index)))
        self.popped_out_loggers[logger.name] = logger
        self.loggerTabWidget.removeTab(index)
        logger.popped_out = True
        logger.show()
        center_widget_on_screen(logger)

    def pop_in_tabs_dialog(self):
        d = PopInDialog(self, self.loggers_by_name.values())
        d.pop_in_tabs.connect(self.pop_in_tabs)
        d.setWindowModality(Qt.ApplicationModal)
        d.open()

    def pop_in_tabs(self, names):
        for name in names:
            self.log.debug('Popping in logger "{}"'.format(name))
            logger = self.loggers_by_name[name]
            self.pop_in_tab(logger)

    def pop_in_tab(self, logger):
        logger.setWindowFlags(Qt.Widget)
        logger.setAttribute(Qt.WA_DeleteOnClose, False)
        logger.destroyed.disconnect(logger.closeEvent)
        logger.setWindowTitle(logger.name)
        logger.popped_out = False
        del self.popped_out_loggers[logger.name]
        index = self.loggerTabWidget.addTab(logger, logger.windowTitle())
        self.loggerTabWidget.setCurrentIndex(index)

    def open_load_records_dialog(self):
        d = QFileDialog(self)
        d.setFileMode(QFileDialog.ExistingFile)
        d.fileSelected.connect(self.load_records)
        d.setWindowTitle('Load records from...')
        d.open()

    def load_records(self, load_path):
        import json
        from os import path

        class RecordDecoder(json.JSONDecoder):
            def __init__(self, *args, **kwargs):
                json.JSONDecoder.__init__(self,
                                          object_hook=self.object_hook,
                                          *args,
                                          **kwargs)

            def object_hook(self, obj):
                if '_created' in obj:
                    obj['created'] = obj['_created']
                    del obj['_created']
                    record = LogRecord(obj)
                    del record._logDict['created']
                else:
                    record = LogRecord(obj)
                return record

        name = path.basename(load_path)
        index = None

        try:
            with open(load_path, 'r') as f:
                records = json.load(f, cls=RecordDecoder)
                new_logger, index = self.create_logger(None, name)
                new_logger.merge_with_records(records)
                self.loggerTabWidget.setCurrentIndex(index)
            self.set_status('Records have been loaded into "{}" tab'.format(
                new_logger.name))
        except Exception as e:
            if index:
                self.close_tab(index)
            text = "Error while loading records: \n{}".format(e)
            self.log.error(text, exc_info=True)
            show_critical_dialog(self, "Couldn't load records", text)

    def open_save_records_dialog(self):
        from functools import partial
        logger, _ = self.current_logger_and_index()
        if not logger:
            return

        d = QFileDialog(self)
        d.selectFile(logger.name + '.log')
        d.setFileMode(QFileDialog.AnyFile)
        d.fileSelected.connect(partial(self.save_records, logger))
        d.setWindowTitle('Save records of "{}" tab to...'.format(logger.name))
        d.open()

    def save_records(self, logger, path):
        import json

        # needed because a deque is not serializable
        class RecordList(list):
            def __init__(self, records):
                self.records = records

            def __len__(self):
                return len(self.records)

            def __iter__(self):
                for record in self.records:
                    d = record._logDict
                    if not d.get('created', False) and not d.get(
                            'time', False):
                        d['_created'] = record.created
                    yield d

        try:
            records = logger.record_model.records
            record_list = RecordList(records)
            with open(path, 'w') as f:
                json.dump(record_list, f, indent=2)
            self.set_status('Records have been saved to "{}"'.format(path))

        except Exception as e:
            text = "Error while saving records: \n{}".format(e)
            self.log.error(text, exc_info=True)
            show_critical_dialog(self, "Couldn't save records", text)

    def closeEvent(self, event):
        self.log.info('Close event on main window')
        self.shutdown()
        event.ignore(
        )  # prevents errors due to closing the program before server has stopped

    def destroy_all_tabs(self):
        self.log.debug('Destroying tabs')
        delete_this = list(self.loggers_by_name.values()
                           )  # to prevent changing during iteration
        for logger in delete_this:
            self.destroy_logger(logger)

    def shutdown(self):
        self.log.info('Shutting down')
        if self.shutting_down:
            self.log.error('Exiting forcefully')
            raise SystemExit
        self.shutting_down = True
        self.stop_server()
        self.save_geometry()
        self.destroy_all_tabs()
        self.app.quit()

    def signal_handler(self, *args):
        self.shutdown()
コード例 #8
0
class PlotWindow(QMainWindow):


    def __init__(self, config_file, parent):
        QMainWindow.__init__(self, parent)

        self._api = PlotApi(ERT.enkf_facade)

        self.setMinimumWidth(850)
        self.setMinimumHeight(650)

        self.setWindowTitle("Plotting - {}".format(config_file))
        self.activateWindow()
        self._key_definitions = self._api.all_data_type_keys()
        self._plot_customizer = PlotCustomizer(self, self._key_definitions)

        self._plot_customizer.settingsChanged.connect(self.keySelected)

        self._central_tab = QTabWidget()

        central_widget = QWidget()
        central_layout = QVBoxLayout()
        central_layout.setContentsMargins(0, 0, 0, 0)
        central_widget.setLayout(central_layout)

        central_layout.addWidget(self._central_tab)

        self.setCentralWidget(central_widget)

        self._plot_widgets = []
        """:type: list of PlotWidget"""

        self.addPlotWidget(ENSEMBLE, EnsemblePlot())
        self.addPlotWidget(STATISTICS, StatisticsPlot())
        self.addPlotWidget(HISTOGRAM, HistogramPlot())
        self.addPlotWidget(GAUSSIAN_KDE, GaussianKDEPlot())
        self.addPlotWidget(DISTRIBUTION, DistributionPlot())
        self.addPlotWidget(CROSS_CASE_STATISTICS, CrossCaseStatisticsPlot())

        self._central_tab.currentChanged.connect(self.currentPlotChanged)


        cases = self._api.get_all_cases_not_running()
        case_names = [case["name"] for case in cases if not case["hidden"]]


        self._data_type_keys_widget = DataTypeKeysWidget(self._key_definitions)
        self._data_type_keys_widget.dataTypeKeySelected.connect(self.keySelected)
        self.addDock("Data types", self._data_type_keys_widget)
        self._case_selection_widget = CaseSelectionWidget(case_names)
        self._case_selection_widget.caseSelectionChanged.connect(self.keySelected)
        self.addDock("Plot case", self._case_selection_widget)

        current_plot_widget = self._plot_widgets[self._central_tab.currentIndex()]
        self._data_type_keys_widget.selectDefault()
        self._updateCustomizer(current_plot_widget)


    def currentPlotChanged(self):
        key_def = self.getSelectedKey()
        key = key_def["key"]

        for plot_widget in self._plot_widgets:
            index = self._central_tab.indexOf(plot_widget)

            if index == self._central_tab.currentIndex() \
                    and plot_widget._plotter.dimensionality == key_def["dimensionality"]:
                self._updateCustomizer(plot_widget)
                cases = self._case_selection_widget.getPlotCaseNames()
                case_to_data_map = {case: self._api.data_for_key(case, key)[key] for case in cases}
                if len(key_def["observations"]) > 0:
                    observations = self._api.observations_for_obs_keys(cases[0], key_def["observations"])
                else:
                    observations = None

                plot_config = PlotConfig.createCopy(self._plot_customizer.getPlotConfig())
                plot_config.setTitle(key)
                plot_context = PlotContext(plot_config, cases, key)

                if key_def["has_refcase"]:
                    plot_context.refcase_data = self._api.refcase_data(key)

                plot_widget.updatePlot(plot_context, case_to_data_map, observations)
コード例 #9
0
class CentralWidget(QWidget):
    """The PyNetAnalyzer central widget"""
    def __init__(self, parent):
        QWidget.__init__(self)
        self.parent = parent
        self.appdata: CnaData = parent.appdata
        self.map_counter = 0
        self.searchbar = QLineEdit()
        self.searchbar.setPlaceholderText("Enter search term")

        self.throttler = SignalThrottler(300)
        self.searchbar.textChanged.connect(self.throttler.throttle)
        self.throttler.triggered.connect(self.update_selected)

        self.tabs = QTabWidget()
        self.reaction_list = ReactionList(self.appdata)
        self.metabolite_list = MetaboliteList(self.appdata)
        self.model_info = ModelInfo(self.appdata)
        self.tabs.addTab(self.reaction_list, "Reactions")
        self.tabs.addTab(self.metabolite_list, "Metabolites")
        self.tabs.addTab(self.model_info, "Model")

        self.map_tabs = QTabWidget()
        self.map_tabs.setTabsClosable(True)
        self.map_tabs.setMovable(True)

        # Create an in-process kernel
        kernel_manager = QtInProcessKernelManager()
        kernel_manager.start_kernel(show_banner=False)
        kernel = kernel_manager.kernel
        kernel.gui = 'qt'

        myglobals = globals()
        myglobals["cna"] = self.parent
        self.kernel_shell = kernel_manager.kernel.shell
        self.kernel_shell.push(myglobals)
        self.kernel_client = kernel_manager.client()
        self.kernel_client.start_channels()

        # Check if client is working
        self.kernel_client.execute('import matplotlib.pyplot as plt')
        self.kernel_client.execute('%matplotlib inline')
        self.kernel_client.execute(
            "%config InlineBackend.figure_format = 'svg'")
        self.console = RichJupyterWidget()
        self.console.kernel_manager = kernel_manager
        self.console.kernel_client = self.kernel_client

        self.splitter = QSplitter()
        self.splitter2 = QSplitter()
        self.splitter2.addWidget(self.map_tabs)
        self.mode_navigator = ModeNavigator(self.appdata)
        self.splitter2.addWidget(self.mode_navigator)
        self.splitter2.addWidget(self.console)
        self.splitter2.setOrientation(Qt.Vertical)
        self.splitter.addWidget(self.splitter2)
        self.splitter.addWidget(self.tabs)
        self.console.show()

        layout = QVBoxLayout()
        layout.addWidget(self.searchbar)
        layout.addWidget(self.splitter)
        self.setLayout(layout)

        self.tabs.currentChanged.connect(self.tabs_changed)
        self.reaction_list.jumpToMap.connect(self.jump_to_map)
        self.reaction_list.jumpToMetabolite.connect(self.jump_to_metabolite)
        self.reaction_list.reactionChanged.connect(
            self.handle_changed_reaction)
        self.reaction_list.reactionDeleted.connect(
            self.handle_deleted_reaction)
        self.metabolite_list.metaboliteChanged.connect(
            self.handle_changed_metabolite)
        self.metabolite_list.jumpToReaction.connect(self.jump_to_reaction)
        self.metabolite_list.computeInOutFlux.connect(self.in_out_fluxes)
        self.model_info.optimizationDirectionChanged.connect(
            self.handle_changed_optimization_direction)
        self.map_tabs.tabCloseRequested.connect(self.delete_map)
        self.mode_navigator.changedCurrentMode.connect(self.update_mode)
        self.mode_navigator.modeNavigatorClosed.connect(self.update)

        self.update()

    def fit_mapview(self):
        self.map_tabs.currentWidget().fit()

    def show_bottom_of_console(self):
        (_, r) = self.splitter2.getRange(1)
        self.splitter2.moveSplitter(r * 0.5, 1)

        vSB = self.console.children()[2].verticalScrollBar()
        max_scroll = vSB.maximum()
        vSB.setValue(max_scroll - 100)

    def handle_changed_reaction(self, old_id: str, reaction: cobra.Reaction):
        self.parent.unsaved_changes()
        for mmap in self.appdata.project.maps:
            if old_id in self.appdata.project.maps[mmap]["boxes"].keys():
                self.appdata.project.maps[mmap]["boxes"][
                    reaction.
                    id] = self.appdata.project.maps[mmap]["boxes"].pop(old_id)

        # TODO update only relevant reaction boxes on maps
        self.update_maps()

    def handle_deleted_reaction(self, reaction: cobra.Reaction):
        self.appdata.project.cobra_py_model.remove_reactions(
            [reaction], remove_orphans=True)

        self.parent.unsaved_changes()
        for mmap in self.appdata.project.maps:
            if reaction.id in self.appdata.project.maps[mmap]["boxes"].keys():
                self.appdata.project.maps[mmap]["boxes"].pop(reaction.id)

        # TODO update only relevant reaction boxes on maps
        self.update_maps()

    def handle_changed_metabolite(self, old_id: str,
                                  metabolite: cobra.Metabolite):
        self.parent.unsaved_changes()
        # TODO update only relevant reaction boxes on maps
        self.update_maps()

    def handle_changed_optimization_direction(self, direction: str):
        self.parent.unsaved_changes()

    def shutdown_kernel(self):
        self.console.kernel_client.stop_channels()
        self.console.kernel_manager.shutdown_kernel()

    def switch_to_reaction(self, reaction: str):
        self.tabs.setCurrentIndex(0)
        self.reaction_list.set_current_item(reaction)

    def minimize_reaction(self, reaction: str):
        self.parent.fba_optimize_reaction(reaction, mmin=True)

    def maximize_reaction(self, reaction: str):
        self.parent.fba_optimize_reaction(reaction, mmin=False)

    def update_reaction_value(self, reaction: str, value: str):
        if value == "":
            self.appdata.scen_values_pop(reaction)
            self.appdata.project.comp_values.pop(reaction, None)
        else:
            try:
                x = float(value)
                self.appdata.scen_values_set(reaction, (x, x))
            except ValueError:
                (vl, vh) = make_tuple(value)
                self.appdata.scen_values_set(reaction, (vl, vh))
        self.reaction_list.update()

    def update_reaction_maps(self, _reaction: str):
        self.parent.unsaved_changes()
        self.reaction_list.reaction_mask.update_state()

    def handle_mapChanged(self, _reaction: str):
        self.parent.unsaved_changes()

    def tabs_changed(self, idx):
        if idx == 0:
            self.reaction_list.update()
        elif idx == 1:
            (clean_model, unused_mets) = prune_unused_metabolites(
                self.appdata.project.cobra_py_model)
            self.appdata.project.cobra_py_model = clean_model
            self.metabolite_list.update()
        elif idx == 2:
            self.model_info.update()

    def add_map(self):
        while True:
            name = "Map " + str(self.map_counter)
            self.map_counter += 1
            if name not in self.appdata.project.maps.keys():
                break
        m = CnaMap(name)

        self.appdata.project.maps[name] = m
        mmap = MapView(self.appdata, name)
        mmap.switchToReactionMask.connect(self.switch_to_reaction)
        mmap.minimizeReaction.connect(self.minimize_reaction)
        mmap.maximizeReaction.connect(self.maximize_reaction)

        mmap.reactionValueChanged.connect(self.update_reaction_value)
        mmap.reactionRemoved.connect(self.update_reaction_maps)
        mmap.reactionAdded.connect(self.update_reaction_maps)
        mmap.mapChanged.connect(self.handle_mapChanged)
        self.map_tabs.addTab(mmap, m["name"])
        self.update_maps()
        self.map_tabs.setCurrentIndex(len(self.appdata.project.maps))
        self.parent.unsaved_changes()

    def delete_map(self, idx: int):
        name = self.map_tabs.tabText(idx)
        diag = ConfirmMapDeleteDialog(self, idx, name)
        diag.exec()

    def update_selected(self):
        x = self.searchbar.text()
        idx = self.tabs.currentIndex()
        if idx == 0:
            self.reaction_list.update_selected(x)
        if idx == 1:
            self.metabolite_list.update_selected(x)

        idx = self.map_tabs.currentIndex()
        if idx >= 0:
            m = self.map_tabs.widget(idx)
            m.update_selected(x)

    def update_mode(self):
        if len(self.appdata.project.modes) > self.mode_navigator.current:
            values = self.appdata.project.modes[self.mode_navigator.current]

            # set values
            self.appdata.project.scen_values.clear()
            self.appdata.project.comp_values.clear()
            for i in values:
                self.appdata.project.comp_values[i] = (values[i], values[i])

        self.appdata.modes_coloring = True
        self.update()
        self.appdata.modes_coloring = False

    def update(self):
        if len(self.appdata.project.modes) == 0:
            self.mode_navigator.hide()
            self.mode_navigator.current = 0
        else:
            self.mode_navigator.show()
            self.mode_navigator.update()

        idx = self.tabs.currentIndex()
        if idx == 0:
            self.reaction_list.update()
        elif idx == 1:
            self.metabolite_list.update()
        elif idx == 2:
            self.model_info.update()

        idx = self.map_tabs.currentIndex()
        if idx >= 0:
            m = self.map_tabs.widget(idx)
            m.update()

    def update_map(self, idx):
        m = self.map_tabs.widget(idx)
        if m is not None:
            m.update()

    def update_maps(self):
        for idx in range(0, self.map_tabs.count()):
            m = self.map_tabs.widget(idx)
            m.update()

    def jump_to_map(self, identifier: str, reaction: str):
        for idx in range(0, self.map_tabs.count()):
            name = self.map_tabs.tabText(idx)
            if name == identifier:
                m = self.map_tabs.widget(idx)
                self.map_tabs.setCurrentIndex(idx)

                m.update()
                m.focus_reaction(reaction)
                m.highlight_reaction(reaction)
                break

    def jump_to_metabolite(self, metabolite: str):
        self.tabs.setCurrentIndex(1)
        m = self.tabs.widget(1)
        m.set_current_item(metabolite)

    def jump_to_reaction(self, reaction: str):
        self.tabs.setCurrentIndex(0)
        m = self.tabs.widget(0)
        m.set_current_item(reaction)

    def in_out_fluxes(self, metabolite):
        self.kernel_client.execute("cna.print_in_out_fluxes('" + metabolite +
                                   "')")
        self.show_bottom_of_console()
コード例 #10
0
class CreatePlan(QWidget):

    plan_created = Signal()
    plan_node_changed = Signal()

    def __init__(self, settings: PartSettings):
        super().__init__()
        self.settings = settings
        self.save_translate_dict: typing.Dict[str, SaveBase] = {x.get_short_name(): x for x in save_dict.values()}
        self.plan = PlanPreview(self)
        self.save_plan_btn = QPushButton("Save")
        self.clean_plan_btn = QPushButton("Remove all")
        self.remove_btn = QPushButton("Remove")
        self.update_element_chk = QCheckBox("Update element")
        self.change_root = EnumComboBox(RootType)
        self.save_choose = QComboBox()
        self.save_choose.addItem("<none>")
        self.save_choose.addItems(list(self.save_translate_dict.keys()))
        self.save_btn = QPushButton("Save")
        self.segment_profile = SearchableListWidget()
        self.pipeline_profile = SearchableListWidget()
        self.segment_stack = QTabWidget()
        self.segment_stack.addTab(self.segment_profile, "Profile")
        self.segment_stack.addTab(self.pipeline_profile, "Pipeline")
        self.generate_mask_btn = QPushButton("Add mask")
        self.generate_mask_btn.setToolTip("Mask need to have unique name")
        self.mask_name = QLineEdit()
        self.mask_operation = EnumComboBox(MaskOperation)

        self.chanel_num = QSpinBox()
        self.choose_channel_for_measurements = QComboBox()
        self.choose_channel_for_measurements.addItems(
            ["Same as segmentation"] + [str(x + 1) for x in range(MAX_CHANNEL_NUM)]
        )
        self.units_choose = EnumComboBox(Units)
        self.units_choose.set_value(self.settings.get("units_value", Units.nm))
        self.chanel_num.setRange(0, 10)
        self.expected_node_type = None
        self.save_constructor = None

        self.chose_profile_btn = QPushButton("Add Profile")
        self.get_big_btn = QPushButton("Leave the biggest")
        self.get_big_btn.hide()
        self.get_big_btn.setDisabled(True)
        self.measurements_list = SearchableListWidget(self)
        self.measurement_name_prefix = QLineEdit(self)
        self.add_calculation_btn = QPushButton("Add measurement calculation")
        self.information = QTextEdit()
        self.information.setReadOnly(True)

        self.protect = False
        self.mask_set = set()
        self.calculation_plan = CalculationPlan()
        self.plan.set_plan(self.calculation_plan)
        self.segmentation_mask = MaskWidget(settings)
        self.file_mask = FileMask()

        self.change_root.currentIndexChanged.connect(self.change_root_type)
        self.save_choose.currentTextChanged.connect(self.save_changed)
        self.measurements_list.currentTextChanged.connect(self.show_measurement)
        self.segment_profile.currentTextChanged.connect(self.show_segment)
        self.measurements_list.currentTextChanged.connect(self.show_measurement_info)
        self.segment_profile.currentTextChanged.connect(self.show_segment_info)
        self.pipeline_profile.currentTextChanged.connect(self.show_segment_info)
        self.pipeline_profile.currentTextChanged.connect(self.show_segment)
        self.mask_name.textChanged.connect(self.mask_name_changed)
        self.generate_mask_btn.clicked.connect(self.create_mask)
        self.clean_plan_btn.clicked.connect(self.clean_plan)
        self.remove_btn.clicked.connect(self.remove_element)
        self.mask_name.textChanged.connect(self.mask_text_changed)
        self.chose_profile_btn.clicked.connect(self.add_segmentation)
        self.get_big_btn.clicked.connect(self.add_leave_biggest)
        self.add_calculation_btn.clicked.connect(self.add_measurement)
        self.save_plan_btn.clicked.connect(self.add_calculation_plan)
        # self.forgot_mask_btn.clicked.connect(self.forgot_mask)
        # self.cmap_save_btn.clicked.connect(self.save_to_cmap)
        self.save_btn.clicked.connect(self.add_save_to_project)
        self.update_element_chk.stateChanged.connect(self.mask_text_changed)
        self.update_element_chk.stateChanged.connect(self.show_measurement)
        self.update_element_chk.stateChanged.connect(self.show_segment)
        self.update_element_chk.stateChanged.connect(self.update_names)
        self.segment_stack.currentChanged.connect(self.change_segmentation_table)

        plan_box = QGroupBox("Prepare workflow:")
        lay = QVBoxLayout()
        lay.addWidget(self.plan)
        bt_lay = QGridLayout()
        bt_lay.setSpacing(1)
        bt_lay.addWidget(self.save_plan_btn, 0, 0)
        bt_lay.addWidget(self.clean_plan_btn, 0, 1)
        bt_lay.addWidget(self.remove_btn, 1, 0)
        bt_lay.addWidget(self.update_element_chk, 1, 1)
        lay.addLayout(bt_lay)
        plan_box.setLayout(lay)
        plan_box.setStyleSheet(group_sheet)

        other_box = QGroupBox("Other operations:")
        other_box.setContentsMargins(0, 0, 0, 0)
        bt_lay = QVBoxLayout()
        bt_lay.setSpacing(0)
        bt_lay.addWidget(QLabel("Root type:"))
        bt_lay.addWidget(self.change_root)
        bt_lay.addStretch(1)
        bt_lay.addWidget(QLabel("Saving:"))
        bt_lay.addWidget(self.save_choose)
        bt_lay.addWidget(self.save_btn)
        other_box.setLayout(bt_lay)
        other_box.setStyleSheet(group_sheet)

        mask_box = QGroupBox("Use mask from:")
        mask_box.setStyleSheet(group_sheet)
        self.mask_stack = QTabWidget()

        self.mask_stack.addTab(stretch_widget(self.file_mask), "File")
        self.mask_stack.addTab(stretch_widget(self.segmentation_mask), "Current ROI")
        self.mask_stack.addTab(stretch_widget(self.mask_operation), "Operations on masks")
        self.mask_stack.setTabToolTip(2, "Allows to create mask which is based on masks previously added to plan.")

        lay = QGridLayout()
        lay.setSpacing(0)
        lay.addWidget(self.mask_stack, 0, 0, 1, 2)
        label = QLabel("Mask name:")
        label.setToolTip("Needed if you would like to reuse this mask in tab 'Operations on masks'")
        self.mask_name.setToolTip("Needed if you would like to reuse this mask in tab 'Operations on masks'")
        lay.addWidget(label, 1, 0)
        lay.addWidget(self.mask_name, 1, 1)
        lay.addWidget(self.generate_mask_btn, 2, 0, 1, 2)
        mask_box.setLayout(lay)

        segment_box = QGroupBox("ROI extraction:")
        segment_box.setStyleSheet(group_sheet)
        lay = QVBoxLayout()
        lay.setSpacing(0)
        lay.addWidget(self.segment_stack)
        lay.addWidget(self.chose_profile_btn)
        lay.addWidget(self.get_big_btn)
        segment_box.setLayout(lay)

        measurement_box = QGroupBox("Set of measurements:")
        measurement_box.setStyleSheet(group_sheet)
        lay = QGridLayout()
        lay.setSpacing(0)
        lay.addWidget(self.measurements_list, 0, 0, 1, 2)
        lab = QLabel("Name prefix:")
        lab.setToolTip("Prefix added before each column name")
        lay.addWidget(lab, 1, 0)
        lay.addWidget(self.measurement_name_prefix, 1, 1)
        lay.addWidget(QLabel("Channel:"), 2, 0)
        lay.addWidget(self.choose_channel_for_measurements, 2, 1)
        lay.addWidget(QLabel("Units:"), 3, 0)
        lay.addWidget(self.units_choose, 3, 1)
        lay.addWidget(self.add_calculation_btn, 4, 0, 1, 2)
        measurement_box.setLayout(lay)

        info_box = QGroupBox("Information")
        info_box.setStyleSheet(group_sheet)
        lay = QVBoxLayout()
        lay.addWidget(self.information)
        info_box.setLayout(lay)

        layout = QGridLayout()
        fst_col = QVBoxLayout()
        fst_col.addWidget(plan_box, 1)
        fst_col.addWidget(mask_box)
        layout.addWidget(plan_box, 0, 0, 5, 1)
        # layout.addWidget(plan_box, 0, 0, 3, 1)
        # layout.addWidget(mask_box, 3, 0, 2, 1)
        # layout.addWidget(segmentation_mask_box, 1, 1)
        layout.addWidget(mask_box, 0, 2, 1, 2)
        layout.addWidget(other_box, 0, 1)
        layout.addWidget(segment_box, 1, 1, 1, 2)
        layout.addWidget(measurement_box, 1, 3)
        layout.addWidget(info_box, 3, 1, 1, 3)
        self.setLayout(layout)

        self.generate_mask_btn.setDisabled(True)
        self.chose_profile_btn.setDisabled(True)
        self.add_calculation_btn.setDisabled(True)

        self.mask_allow = False
        self.segment_allow = False
        self.file_mask_allow = False
        self.node_type = NodeType.root
        self.node_name = ""
        self.plan_node_changed.connect(self.mask_text_changed)
        self.plan.changed_node.connect(self.node_type_changed)
        self.plan_node_changed.connect(self.show_segment)
        self.plan_node_changed.connect(self.show_measurement)
        self.plan_node_changed.connect(self.mask_stack_change)
        self.mask_stack.currentChanged.connect(self.mask_stack_change)
        self.file_mask.value_changed.connect(self.mask_stack_change)
        self.mask_name.textChanged.connect(self.mask_stack_change)
        self.node_type_changed()

    def change_root_type(self):
        value: RootType = self.change_root.get_value()
        self.calculation_plan.set_root_type(value)
        self.plan.update_view()

    def change_segmentation_table(self):
        index = self.segment_stack.currentIndex()
        text = self.segment_stack.tabText(index)
        if self.update_element_chk.isChecked():
            self.chose_profile_btn.setText("Replace " + text)
        else:
            self.chose_profile_btn.setText("Add " + text)
        self.segment_profile.setCurrentItem(None)
        self.pipeline_profile.setCurrentItem(None)

    def save_changed(self, text):
        text = str(text)
        if text == "<none>":
            self.save_btn.setText("Save")
            self.save_btn.setToolTip("Choose file type")
            self.expected_node_type = None
            self.save_constructor = None
        else:
            save_class = self.save_translate_dict.get(text, None)
            if save_class is None:
                self.save_choose.setCurrentText("<none>")
                return
            self.save_btn.setText(f"Save to {save_class.get_short_name()}")
            self.save_btn.setToolTip("Choose mask create in plan view")
            if save_class.need_mask():
                self.expected_node_type = NodeType.mask
            elif save_class.need_segmentation():
                self.expected_node_type = NodeType.segment
            else:
                self.expected_node_type = NodeType.root
            self.save_constructor = Save
        self.save_activate()

    def save_activate(self):
        self.save_btn.setDisabled(True)
        if self.node_type == self.expected_node_type:
            self.save_btn.setEnabled(True)
            return

    def segmentation_from_project(self):
        self.calculation_plan.add_step(Operations.reset_to_base)
        self.plan.update_view()

    def update_names(self):
        if self.update_element_chk.isChecked():
            self.chose_profile_btn.setText("Replace Profile")
            self.add_calculation_btn.setText("Replace set of measurements")
            self.generate_mask_btn.setText("Replace mask")
        else:
            self.chose_profile_btn.setText("Add Profile")
            self.add_calculation_btn.setText("Add set of measurements")
            self.generate_mask_btn.setText("Generate mask")

    def node_type_changed(self):
        # self.cmap_save_btn.setDisabled(True)
        self.save_btn.setDisabled(True)
        self.node_name = ""
        if self.plan.currentItem() is None:
            self.mask_allow = False
            self.file_mask_allow = False
            self.segment_allow = False
            self.remove_btn.setDisabled(True)
            self.plan_node_changed.emit()
            logging.debug("[node_type_changed] return")
            return
        node_type = self.calculation_plan.get_node_type()
        self.node_type = node_type
        if node_type in [NodeType.file_mask, NodeType.mask, NodeType.segment, NodeType.measurement, NodeType.save]:
            self.remove_btn.setEnabled(True)
        else:
            self.remove_btn.setEnabled(False)
        if node_type in (NodeType.mask, NodeType.file_mask):
            self.mask_allow = False
            self.segment_allow = True
            self.file_mask_allow = False
            self.node_name = self.calculation_plan.get_node().operation.name
        elif node_type == NodeType.segment:
            self.mask_allow = True
            self.segment_allow = False
            self.file_mask_allow = False
            self.save_btn.setEnabled(True)
            # self.cmap_save_btn.setEnabled(True)
        elif node_type == NodeType.root:
            self.mask_allow = False
            self.segment_allow = True
            self.file_mask_allow = True
        elif node_type in (NodeType.none, NodeType.measurement, NodeType.save):
            self.mask_allow = False
            self.segment_allow = False
            self.file_mask_allow = False
        self.save_activate()
        self.plan_node_changed.emit()

    def add_save_to_project(self):
        save_class = self.save_translate_dict.get(self.save_choose.currentText(), None)
        if save_class is None:
            QMessageBox.warning(self, "Save problem", "Not found save class")
        dial = FormDialog(
            [AlgorithmProperty("suffix", "File suffix", ""), AlgorithmProperty("directory", "Sub directory", "")]
            + save_class.get_fields()
        )
        if dial.exec():
            values = dial.get_values()
            suffix = values["suffix"]
            directory = values["directory"]
            del values["suffix"]
            del values["directory"]
            save_elem = Save(suffix, directory, save_class.get_name(), save_class.get_short_name(), values)
            if self.update_element_chk.isChecked():
                self.calculation_plan.replace_step(save_elem)
            else:
                self.calculation_plan.add_step(save_elem)
            self.plan.update_view()

    def create_mask(self):
        text = str(self.mask_name.text()).strip()

        if text != "" and text in self.mask_set:
            QMessageBox.warning(self, "Already exists", "Mask with this name already exists", QMessageBox.Ok)
            return
        if _check_widget(self.mask_stack, EnumComboBox):  # existing mask
            mask_dialog = TwoMaskDialog
            if self.mask_operation.get_value() == MaskOperation.mask_intersection:  # Mask intersection
                MaskConstruct = MaskIntersection
            else:
                MaskConstruct = MaskSum
            dial = mask_dialog(self.mask_set)
            if not dial.exec():
                return
            names = dial.get_result()

            mask_ob = MaskConstruct(text, *names)
        elif _check_widget(self.mask_stack, MaskWidget):
            mask_ob = MaskCreate(text, self.segmentation_mask.get_mask_property())
        elif _check_widget(self.mask_stack, FileMask):
            mask_ob = self.file_mask.get_value(text)
        else:
            raise ValueError("Unknowsn widget")

        if self.update_element_chk.isChecked():
            node = self.calculation_plan.get_node()
            name = node.operation.name
            if name in self.calculation_plan.get_reused_mask() and name != text:
                QMessageBox.warning(
                    self, "Cannot remove", f"Cannot remove mask '{name}' from plan because it is used in other elements"
                )
                return

            self.mask_set.remove(name)
            self.mask_set.add(mask_ob.name)
            self.calculation_plan.replace_step(mask_ob)
        else:
            self.mask_set.add(mask_ob.name)
            self.calculation_plan.add_step(mask_ob)
        self.plan.update_view()
        self.mask_text_changed()

    def mask_stack_change(self):
        node_type = self.calculation_plan.get_node_type()
        if self.update_element_chk.isChecked() and node_type not in [NodeType.mask, NodeType.file_mask]:
            self.generate_mask_btn.setDisabled(True)
        text = self.mask_name.text()
        update = self.update_element_chk.isChecked()
        if self.node_type == NodeType.none:
            self.generate_mask_btn.setDisabled(True)
            return
        operation = self.calculation_plan.get_node().operation
        if (
            not update
            and isinstance(operation, (MaskMapper, MaskBase))
            and self.calculation_plan.get_node().operation.name == text
        ):
            self.generate_mask_btn.setDisabled(True)
            return
        if _check_widget(self.mask_stack, EnumComboBox):  # reuse mask
            if len(self.mask_set) > 1 and (
                (not update and node_type == NodeType.root) or (update and node_type == NodeType.file_mask)
            ):
                self.generate_mask_btn.setEnabled(True)
            else:
                self.generate_mask_btn.setEnabled(False)
            self.generate_mask_btn.setToolTip("Need at least two named mask and root selected")
        elif _check_widget(self.mask_stack, MaskWidget):  # mask from segmentation
            if (not update and node_type == NodeType.segment) or (update and node_type == NodeType.mask):
                self.generate_mask_btn.setEnabled(True)
            else:
                self.generate_mask_btn.setEnabled(False)
            self.generate_mask_btn.setToolTip("Select segmentation")
        else:
            if (not update and node_type == NodeType.root) or (update and node_type == NodeType.file_mask):
                self.generate_mask_btn.setEnabled(self.file_mask.is_valid())
            else:
                self.generate_mask_btn.setEnabled(False)
            self.generate_mask_btn.setToolTip("Need root selected")

    def mask_name_changed(self, text):
        if str(text) in self.mask_set:
            self.generate_mask_btn.setDisabled(True)
        else:
            self.generate_mask_btn.setDisabled(False)

    def add_leave_biggest(self):
        profile = self.calculation_plan.get_node().operation
        profile.leave_biggest_swap()
        self.calculation_plan.replace_step(profile)
        self.plan.update_view()

    def add_segmentation(self):
        if self.segment_stack.currentIndex() == 0:
            text = str(self.segment_profile.currentItem().text())
            if text not in self.settings.segmentation_profiles:
                self.refresh_all_profiles()
                return
            profile = self.settings.segmentation_profiles[text]
            if self.update_element_chk.isChecked():
                self.calculation_plan.replace_step(profile)
            else:
                self.calculation_plan.add_step(profile)
            self.plan.update_view()

        else:  # self.segment_stack.currentIndex() == 1
            text = self.pipeline_profile.currentItem().text()
            segmentation_pipeline = self.settings.segmentation_pipelines[text]
            pos = self.calculation_plan.current_pos[:]
            old_pos = self.calculation_plan.current_pos[:]
            for el in segmentation_pipeline.mask_history:
                self.calculation_plan.add_step(el.segmentation)
                self.plan.update_view()
                node = self.calculation_plan.get_node(pos)
                pos.append(len(node.children) - 1)
                self.calculation_plan.set_position(pos)
                self.calculation_plan.add_step(MaskCreate("", el.mask_property))
                self.plan.update_view()
                pos.append(0)
                self.calculation_plan.set_position(pos)
            self.calculation_plan.add_step(segmentation_pipeline.segmentation)
            self.calculation_plan.set_position(old_pos)
            self.plan.update_view()

    def add_measurement(self):
        text = str(self.measurements_list.currentItem().text())
        measurement_copy = deepcopy(self.settings.measurement_profiles[text])
        prefix = str(self.measurement_name_prefix.text()).strip()
        channel = self.choose_channel_for_measurements.currentIndex() - 1
        measurement_copy.name_prefix = prefix
        # noinspection PyTypeChecker
        measurement_calculate = MeasurementCalculate(
            channel=channel,
            measurement_profile=measurement_copy,
            name_prefix=prefix,
            units=self.units_choose.get_value(),
        )
        if self.update_element_chk.isChecked():
            self.calculation_plan.replace_step(measurement_calculate)
        else:
            self.calculation_plan.add_step(measurement_calculate)
        self.plan.update_view()

    def remove_element(self):
        conflict_mask, used_mask = self.calculation_plan.get_file_mask_names()
        if len(conflict_mask) > 0:
            logging.info("Mask in use")
            QMessageBox.warning(self, "In use", "Masks {} are used in other places".format(", ".join(conflict_mask)))
            return
        self.mask_set -= used_mask
        self.calculation_plan.remove_step()
        self.plan.update_view()

    def clean_plan(self):
        self.calculation_plan = CalculationPlan()
        self.plan.set_plan(self.calculation_plan)
        self.node_type_changed()
        self.mask_set = set()

    def mask_text_changed(self):
        name = str(self.mask_name.text()).strip()
        self.generate_mask_btn.setDisabled(True)
        # load mask from file
        if not self.update_element_chk.isChecked():
            # generate mask from segmentation
            if self.mask_allow and (name == "" or name not in self.mask_set):
                self.generate_mask_btn.setEnabled(True)
        else:
            if self.node_type != NodeType.file_mask and self.node_type != NodeType.mask:
                return
            # generate mask from segmentation
            if self.node_type == NodeType.mask and (name == "" or name == self.node_name or name not in self.mask_set):
                self.generate_mask_btn.setEnabled(True)

    def add_calculation_plan(self, text=None):
        if text is None or isinstance(text, bool):
            text, ok = QInputDialog.getText(self, "Plan title", "Set plan title")
        else:
            text, ok = QInputDialog.getText(
                self, "Plan title", f"Set plan title. Previous ({text}) is already in use", text=text
            )
        text = text.strip()
        if ok:
            if text == "":
                QMessageBox.information(
                    self, "Name cannot be empty", "Name cannot be empty, Please set correct name", QMessageBox.Ok
                )
                self.add_calculation_plan()
                return
            if text in self.settings.batch_plans:
                res = QMessageBox.information(
                    self,
                    "Name already in use",
                    "Name already in use. Would like to overwrite?",
                    QMessageBox.Yes | QMessageBox.No,
                )
                if res == QMessageBox.No:
                    self.add_calculation_plan(text)
                    return
            plan = copy(self.calculation_plan)
            plan.set_name(text)
            self.settings.batch_plans[text] = plan
            self.settings.dump()
            self.plan_created.emit()

    @staticmethod
    def get_index(item: QListWidgetItem, new_values: typing.List[str]) -> int:
        if item is None:
            return -1
        text = item.text()
        try:
            return new_values.index(text)
        except ValueError:
            return -1

    @staticmethod
    def refresh_profiles(list_widget: QListWidget, new_values: typing.List[str], index: int):
        list_widget.clear()
        list_widget.addItems(new_values)
        if index != -1:
            list_widget.setCurrentRow(index)

    def showEvent(self, _event):
        self.refresh_all_profiles()

    def refresh_all_profiles(self):
        new_measurements = list(sorted(self.settings.measurement_profiles.keys()))
        new_segment = list(sorted(self.settings.segmentation_profiles.keys()))
        new_pipelines = list(sorted(self.settings.segmentation_pipelines.keys()))
        measurement_index = self.get_index(self.measurements_list.currentItem(), new_measurements)
        segment_index = self.get_index(self.segment_profile.currentItem(), new_segment)
        pipeline_index = self.get_index(self.pipeline_profile.currentItem(), new_pipelines)
        self.protect = True
        self.refresh_profiles(self.measurements_list, new_measurements, measurement_index)
        self.refresh_profiles(self.segment_profile, new_segment, segment_index)
        self.refresh_profiles(self.pipeline_profile, new_pipelines, pipeline_index)
        self.protect = False

    def show_measurement_info(self, text=None):
        if self.protect:
            return
        if text is None:
            if self.measurements_list.currentItem() is not None:
                text = str(self.measurements_list.currentItem().text())
            else:
                return
        profile = self.settings.measurement_profiles[text]
        self.information.setText(str(profile))

    def show_measurement(self):
        if self.update_element_chk.isChecked():
            if self.node_type == NodeType.measurement:
                self.add_calculation_btn.setEnabled(True)
            else:
                self.add_calculation_btn.setDisabled(True)
        else:
            if self.measurements_list.currentItem() is not None:
                self.add_calculation_btn.setEnabled(self.mask_allow)
            else:
                self.add_calculation_btn.setDisabled(True)

    def show_segment_info(self, text=None):
        if self.protect:
            return
        if text == "":
            return
        if self.segment_stack.currentIndex() == 0:
            if text is None:
                if self.segment_profile.currentItem() is not None:
                    text = str(self.segment_profile.currentItem().text())
                else:
                    return
            profile = self.settings.segmentation_profiles[text]
        else:
            if text is None:
                if self.pipeline_profile.currentItem() is not None:
                    text = str(self.pipeline_profile.currentItem().text())
                else:
                    return
            profile = self.settings.segmentation_pipelines[text]
        self.information.setText(profile.pretty_print(analysis_algorithm_dict))

    def show_segment(self):
        if self.update_element_chk.isChecked() and self.segment_stack.currentIndex() == 0:
            self.get_big_btn.setDisabled(True)
            if self.node_type == NodeType.segment:
                self.chose_profile_btn.setEnabled(True)
            else:
                self.chose_profile_btn.setDisabled(True)
        else:
            if self.node_type == NodeType.segment:
                self.get_big_btn.setEnabled(True)
            else:
                self.get_big_btn.setDisabled(True)
            if (
                self.segment_stack.currentIndex() == 0 and self.segment_profile.currentItem()
            ) or self.pipeline_profile.currentItem() is not None:
                self.chose_profile_btn.setEnabled(self.segment_allow)
            else:
                self.chose_profile_btn.setDisabled(True)

    def edit_plan(self):
        plan = self.sender().plan_to_edit  # type: CalculationPlan
        self.calculation_plan = copy(plan)
        self.plan.set_plan(self.calculation_plan)
        self.mask_set.clear()
        self.calculation_plan.set_position([])
        self.mask_set.update(self.calculation_plan.get_mask_names())
コード例 #11
0
ファイル: importwizard.py プロジェクト: ShenggaoZhu/spyder
class ImportWizard(QDialog):
    """Text data import wizard"""
    def __init__(self, parent, text,
                 title=None, icon=None, contents_title=None, varname=None):
        QDialog.__init__(self, parent)

        # Destroying the C++ object right after closing the dialog box,
        # otherwise it may be garbage-collected in another QThread
        # (e.g. the editor's analysis thread in Spyder), thus leading to
        # a segmentation fault on UNIX or an application crash on Windows
        self.setAttribute(Qt.WA_DeleteOnClose)

        if title is None:
            title = _("Import wizard")
        self.setWindowTitle(title)
        if icon is None:
            self.setWindowIcon(ima.icon('fileimport'))
        if contents_title is None:
            contents_title = _("Raw text")

        if varname is None:
            varname = _("variable_name")

        self.var_name, self.clip_data = None, None

        # Setting GUI
        self.tab_widget = QTabWidget(self)
        self.text_widget = ContentsWidget(self, text)
        self.table_widget = PreviewWidget(self)

        self.tab_widget.addTab(self.text_widget, _("text"))
        self.tab_widget.setTabText(0, contents_title)
        self.tab_widget.addTab(self.table_widget, _("table"))
        self.tab_widget.setTabText(1, _("Preview"))
        self.tab_widget.setTabEnabled(1, False)

        name_layout = QHBoxLayout()
        name_label = QLabel(_("Variable Name"))
        name_layout.addWidget(name_label)

        self.name_edt = QLineEdit()
        self.name_edt.setText(varname)
        name_layout.addWidget(self.name_edt)

        btns_layout = QHBoxLayout()
        cancel_btn = QPushButton(_("Cancel"))
        btns_layout.addWidget(cancel_btn)
        cancel_btn.clicked.connect(self.reject)
        h_spacer = QSpacerItem(40, 20,
                               QSizePolicy.Expanding, QSizePolicy.Minimum)
        btns_layout.addItem(h_spacer)
        self.back_btn = QPushButton(_("Previous"))
        self.back_btn.setEnabled(False)
        btns_layout.addWidget(self.back_btn)
        self.back_btn.clicked.connect(ft_partial(self._set_step, step=-1))
        self.fwd_btn = QPushButton(_("Next"))
        btns_layout.addWidget(self.fwd_btn)
        self.fwd_btn.clicked.connect(ft_partial(self._set_step, step=1))
        self.done_btn = QPushButton(_("Done"))
        self.done_btn.setEnabled(False)
        btns_layout.addWidget(self.done_btn)
        self.done_btn.clicked.connect(self.process)

        self.text_widget.asDataChanged.connect(self.fwd_btn.setEnabled)
        self.text_widget.asDataChanged.connect(self.done_btn.setDisabled)
        layout = QVBoxLayout()
        layout.addLayout(name_layout)
        layout.addWidget(self.tab_widget)
        layout.addLayout(btns_layout)
        self.setLayout(layout)

    def _focus_tab(self, tab_idx):
        """Change tab focus"""
        for i in range(self.tab_widget.count()):
            self.tab_widget.setTabEnabled(i, False)
        self.tab_widget.setTabEnabled(tab_idx, True)
        self.tab_widget.setCurrentIndex(tab_idx)

    def _set_step(self, step):
        """Proceed to a given step"""
        new_tab = self.tab_widget.currentIndex() + step
        assert new_tab < self.tab_widget.count() and new_tab >= 0
        if new_tab == self.tab_widget.count()-1:
            try:
                self.table_widget.open_data(self._get_plain_text(),
                                        self.text_widget.get_col_sep(),
                                        self.text_widget.get_row_sep(),
                                        self.text_widget.trnsp_box.isChecked(),
                                        self.text_widget.get_skiprows(),
                                        self.text_widget.get_comments())
                self.done_btn.setEnabled(True)
                self.done_btn.setDefault(True)
                self.fwd_btn.setEnabled(False)
                self.back_btn.setEnabled(True)
            except (SyntaxError, AssertionError) as error:
                QMessageBox.critical(self, _("Import wizard"),
                            _("<b>Unable to proceed to next step</b>"
                              "<br><br>Please check your entries."
                              "<br><br>Error message:<br>%s") % str(error))
                return
        elif new_tab == 0:
            self.done_btn.setEnabled(False)
            self.fwd_btn.setEnabled(True)
            self.back_btn.setEnabled(False)
        self._focus_tab(new_tab)

    def get_data(self):
        """Return processed data"""
        # It is import to avoid accessing Qt C++ object as it has probably
        # already been destroyed, due to the Qt.WA_DeleteOnClose attribute
        return self.var_name, self.clip_data

    def _simplify_shape(self, alist, rec=0):
        """Reduce the alist dimension if needed"""
        if rec != 0:
            if len(alist) == 1:
                return alist[-1]
            return alist
        if len(alist) == 1:
            return self._simplify_shape(alist[-1], 1)
        return [self._simplify_shape(al, 1) for al in alist]

    def _get_table_data(self):
        """Return clipboard processed as data"""
        data = self._simplify_shape(
                self.table_widget.get_data())
        if self.table_widget.array_btn.isChecked():
            return array(data)
        elif pd and self.table_widget.df_btn.isChecked():
            info = self.table_widget.pd_info
            buf = io.StringIO(self.table_widget.pd_text)
            return pd.read_csv(buf, **info)
        return data

    def _get_plain_text(self):
        """Return clipboard as text"""
        return self.text_widget.text_editor.toPlainText()

    @Slot()
    def process(self):
        """Process the data from clipboard"""
        var_name = self.name_edt.text()
        try:
            self.var_name = str(var_name)
        except UnicodeEncodeError:
            self.var_name = to_text_string(var_name)
        if self.text_widget.get_as_data():
            self.clip_data = self._get_table_data()
        elif self.text_widget.get_as_code():
            self.clip_data = try_to_eval(
                to_text_string(self._get_plain_text()))
        else:
            self.clip_data = to_text_string(self._get_plain_text())
        self.accept()
コード例 #12
0
class RunDialog(QDialog):
    simulation_done = Signal(bool, str)
    simulation_termination_request = Signal()

    def __init__(self,
                 config_file,
                 run_model,
                 simulation_arguments,
                 parent=None):
        QDialog.__init__(self, parent)
        self.setWindowFlags(Qt.Window)
        self.setWindowFlags(self.windowFlags()
                            & ~Qt.WindowContextHelpButtonHint)
        self.setModal(True)
        self.setWindowModality(Qt.WindowModal)
        self.setWindowTitle("Simulations - {}".format(config_file))

        self._snapshot_model = SnapshotModel(self)
        self._run_model = run_model

        self._isDetailedDialog = False
        self._minimum_width = 1200

        ert = None
        if isinstance(run_model, BaseRunModel):
            ert = run_model.ert()

        self._simulations_argments = simulation_arguments

        self._ticker = QTimer(self)
        self._ticker.timeout.connect(self._on_ticker)

        progress_proxy_model = ProgressProxyModel(self._snapshot_model,
                                                  parent=self)

        self._total_progress_label = QLabel(_TOTAL_PROGRESS_TEMPLATE.format(0),
                                            self)

        self._total_progress_bar = QProgressBar(self)
        self._total_progress_bar.setRange(0, 100)
        self._total_progress_bar.setTextVisible(False)

        self._iteration_progress_label = QLabel(self)

        self._progress_view = ProgressView(self)
        self._progress_view.setModel(progress_proxy_model)
        self._progress_view.setIndeterminate(True)

        legend_view = LegendView(self)
        legend_view.setModel(progress_proxy_model)

        self._tab_widget = QTabWidget(self)
        self._snapshot_model.rowsInserted.connect(self.on_new_iteration)

        self._job_label = QLabel(self)

        self._job_model = JobListProxyModel(self, 0, 0, 0, 0)
        self._job_model.setSourceModel(self._snapshot_model)

        self._job_view = QTableView(self)
        self._job_view.clicked.connect(self._job_clicked)
        self._open_files = {}
        self._job_view.setModel(self._job_model)

        self.running_time = QLabel("")

        self.plot_tool = PlotTool(config_file)
        self.plot_tool.setParent(self)
        self.plot_button = QPushButton(self.plot_tool.getName())
        self.plot_button.clicked.connect(self.plot_tool.trigger)
        self.plot_button.setEnabled(ert is not None)

        self.kill_button = QPushButton("Kill Simulations")
        self.done_button = QPushButton("Done")
        self.done_button.setHidden(True)
        self.restart_button = QPushButton("Restart")
        self.restart_button.setHidden(True)
        self.show_details_button = QPushButton("Show Details")
        self.show_details_button.setCheckable(True)

        size = 20
        spin_movie = resourceMovie("ide/loading.gif")
        spin_movie.setSpeed(60)
        spin_movie.setScaledSize(QSize(size, size))
        spin_movie.start()

        self.processing_animation = QLabel()
        self.processing_animation.setMaximumSize(QSize(size, size))
        self.processing_animation.setMinimumSize(QSize(size, size))
        self.processing_animation.setMovie(spin_movie)

        button_layout = QHBoxLayout()
        button_layout.addWidget(self.processing_animation)
        button_layout.addWidget(self.running_time)
        button_layout.addStretch()
        button_layout.addWidget(self.show_details_button)
        button_layout.addWidget(self.plot_button)
        button_layout.addWidget(self.kill_button)
        button_layout.addWidget(self.done_button)
        button_layout.addWidget(self.restart_button)

        button_widget_container = QWidget()
        button_widget_container.setLayout(button_layout)

        layout = QVBoxLayout()
        layout.addWidget(self._total_progress_label)
        layout.addWidget(self._total_progress_bar)
        layout.addWidget(self._iteration_progress_label)
        layout.addWidget(self._progress_view)
        layout.addWidget(legend_view)
        layout.addWidget(self._tab_widget)
        layout.addWidget(self._job_label)
        layout.addWidget(self._job_view)
        layout.addWidget(button_widget_container)

        self.setLayout(layout)

        self.kill_button.clicked.connect(self.killJobs)
        self.done_button.clicked.connect(self.accept)
        self.restart_button.clicked.connect(self.restart_failed_realizations)
        self.show_details_button.clicked.connect(self.toggle_detailed_progress)
        self.simulation_done.connect(self._on_simulation_done)

        self.setMinimumWidth(self._minimum_width)
        self._setSimpleDialog()

    def _setSimpleDialog(self) -> None:
        self._isDetailedDialog = False
        self._tab_widget.setVisible(False)
        self._job_label.setVisible(False)
        self._job_view.setVisible(False)
        self.show_details_button.setText("Show Details")

    def _setDetailedDialog(self) -> None:
        self._isDetailedDialog = True
        self._tab_widget.setVisible(True)
        self._job_label.setVisible(True)
        self._job_view.setVisible(True)
        self.show_details_button.setText("Hide Details")

    @Slot(QModelIndex, int, int)
    def on_new_iteration(self, parent: QModelIndex, start: int,
                         end: int) -> None:
        if not parent.isValid():
            iter = start
            self._iteration_progress_label.setText(
                f"Progress for iteration {iter}")

            widget = RealizationWidget(iter)
            widget.setSnapshotModel(self._snapshot_model)
            widget.currentChanged.connect(self._select_real)

            self._tab_widget.addTab(widget,
                                    f"Realizations for iteration {iter}")

    @Slot(QModelIndex)
    def _job_clicked(self, index):
        if not index.isValid():
            return
        selected_file = index.data(FileRole)

        if selected_file and selected_file not in self._open_files:
            job_name = index.siblingAtColumn(0).data()
            viewer = FileDialog(
                selected_file,
                job_name,
                index.row(),
                index.model().get_real(),
                index.model().get_iter(),
                self,
            )
            self._open_files[selected_file] = viewer
            viewer.finished.connect(
                lambda _, f=selected_file: self._open_files.pop(f))

        elif selected_file in self._open_files:
            self._open_files[selected_file].raise_()

    @Slot(QModelIndex)
    def _select_real(self, index):
        step = 0
        stage = 0
        real = index.row()
        iter_ = index.model().get_iter()
        self._job_model.set_step(iter_, real, stage, step)
        self._job_label.setText(
            f"Realization id {index.data(RealIens)} in iteration {iter_}")

        self._job_view.horizontalHeader().setSectionResizeMode(
            QHeaderView.Stretch)

        # Clear the selection in the other tabs
        for i in range(0, self._tab_widget.count()):
            if i != self._tab_widget.currentIndex():
                self._tab_widget.widget(i).clearSelection()

    def reject(self):
        return

    def closeEvent(self, QCloseEvent):
        if self._run_model.isFinished():
            self.simulation_done.emit(self._run_model.hasRunFailed(),
                                      self._run_model.getFailMessage())
        else:
            # Kill jobs if dialog is closed
            if self.killJobs() != QMessageBox.Yes:
                QCloseEvent.ignore()

    def startSimulation(self):
        self._run_model.reset()
        self._snapshot_model.reset()
        self._tab_widget.clear()

        def run():
            asyncio.set_event_loop(asyncio.new_event_loop())
            self._run_model.startSimulations(self._simulations_argments)

        simulation_thread = Thread(name="ert_gui_simulation_thread")
        simulation_thread.setDaemon(True)
        simulation_thread.run = run
        simulation_thread.start()

        self._ticker.start(1000)

        tracker = create_tracker(
            self._run_model,
            num_realizations=self._simulations_argments["active_realizations"].
            count(),
            ee_config=self._simulations_argments.get("ee_config", None),
        )

        worker = TrackerWorker(tracker)
        worker_thread = QThread()
        worker.done.connect(worker_thread.quit)
        worker.consumed_event.connect(self._on_tracker_event)
        worker.moveToThread(worker_thread)
        self.simulation_done.connect(worker.stop)
        self._worker = worker
        self._worker_thread = worker_thread
        worker_thread.started.connect(worker.consume_and_emit)
        self._worker_thread.start()

    def killJobs(self):

        msg = "Are you sure you want to kill the currently running simulations?"
        if self._run_model.getQueueStatus().get(
                JobStatusType.JOB_QUEUE_UNKNOWN, 0) > 0:
            msg += "\n\nKilling a simulation with unknown status will not kill the realizations already submitted!"
        kill_job = QMessageBox.question(self, "Kill simulations?", msg,
                                        QMessageBox.Yes | QMessageBox.No)

        if kill_job == QMessageBox.Yes:
            # Normally this slot would be invoked by the signal/slot system,
            # but the worker is busy tracking the evaluation.
            self._worker.request_termination()
            self.reject()
        return kill_job

    @Slot(bool, str)
    def _on_simulation_done(self, failed, failed_msg):
        self.processing_animation.hide()
        self.kill_button.setHidden(True)
        self.done_button.setHidden(False)
        self.restart_button.setVisible(self.has_failed_realizations())
        self.restart_button.setEnabled(self._run_model.support_restart)
        self._total_progress_bar.setValue(100)
        self._total_progress_label.setText(
            _TOTAL_PROGRESS_TEMPLATE.format(100))

        if failed:
            QMessageBox.critical(
                self,
                "Simulations failed!",
                f"The simulation failed with the following error:\n\n{failed_msg}",
            )

    @Slot()
    def _on_ticker(self):
        runtime = self._run_model.get_runtime()
        self.running_time.setText(format_running_time(runtime))

    @Slot(object)
    def _on_tracker_event(self, event):
        if isinstance(event, EndEvent):
            self.simulation_done.emit(event.failed, event.failed_msg)
            self._worker.stop()
            self._ticker.stop()

        elif isinstance(event, FullSnapshotEvent):
            if event.snapshot is not None:
                self._snapshot_model._add_snapshot(event.snapshot,
                                                   event.iteration)
            self._progress_view.setIndeterminate(event.indeterminate)
            progress = int(event.progress * 100)
            self._total_progress_bar.setValue(progress)
            self._total_progress_label.setText(
                _TOTAL_PROGRESS_TEMPLATE.format(progress))

        elif isinstance(event, SnapshotUpdateEvent):
            if event.partial_snapshot is not None:
                self._snapshot_model._add_partial_snapshot(
                    event.partial_snapshot, event.iteration)
            self._progress_view.setIndeterminate(event.indeterminate)
            progress = int(event.progress * 100)
            self._total_progress_bar.setValue(progress)
            self._total_progress_label.setText(
                _TOTAL_PROGRESS_TEMPLATE.format(progress))

    def has_failed_realizations(self):
        completed = self._run_model.completed_realizations_mask
        initial = self._run_model.initial_realizations_mask
        for (index, successful) in enumerate(completed):
            if initial[index] and not successful:
                return True
        return False

    def count_successful_realizations(self):
        """
        Counts the realizations completed in the prevoius ensemble run
        :return:
        """
        completed = self._run_model.completed_realizations_mask
        return completed.count(True)

    def create_mask_from_failed_realizations(self):
        """
        Creates a BoolVector mask representing the failed realizations
        :return: Type BoolVector
        """
        completed = self._run_model.completed_realizations_mask
        initial = self._run_model.initial_realizations_mask
        inverted_mask = BoolVector(default_value=False)
        for (index, successful) in enumerate(completed):
            inverted_mask[index] = initial[index] and not successful
        return inverted_mask

    def restart_failed_realizations(self):

        msg = QMessageBox(self)
        msg.setIcon(QMessageBox.Information)
        msg.setText(
            "Note that workflows will only be executed on the restarted realizations and that this might have unexpected consequences."
        )
        msg.setWindowTitle("Restart Failed Realizations")
        msg.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
        result = msg.exec_()

        if result == QMessageBox.Ok:
            self.restart_button.setVisible(False)
            self.kill_button.setVisible(True)
            self.done_button.setVisible(False)
            active_realizations = self.create_mask_from_failed_realizations()
            self._simulations_argments[
                "active_realizations"] = active_realizations
            self._simulations_argments[
                "prev_successful_realizations"] = self._simulations_argments.get(
                    "prev_successful_realizations", 0)
            self._simulations_argments[
                "prev_successful_realizations"] += self.count_successful_realizations(
                )
            self.startSimulation()

    @Slot()
    def toggle_detailed_progress(self):
        if self._isDetailedDialog:
            self._setSimpleDialog()
        else:
            self._setDetailedDialog()

        self.adjustSize()
コード例 #13
0
ファイル: plot_window.py プロジェクト: mortalisk/ert
class PlotWindow(QMainWindow):


    def __init__(self, config_file, parent):
        QMainWindow.__init__(self, parent)

        self._ert = ERT.ert
        """:type: res.enkf.enkf_main.EnKFMain"""

        key_manager = self._ert.getKeyManager()
        """:type: res.enkf.key_manager.KeyManager """

        self.setMinimumWidth(850)
        self.setMinimumHeight(650)

        self.setWindowTitle("Plotting - {}".format(config_file))
        self.activateWindow()

        self._plot_customizer = PlotCustomizer(self)

        def plotConfigCreator(key):
            return PlotConfigFactory.createPlotConfigForKey(self._ert, key)

        self._plot_customizer.setPlotConfigCreator(plotConfigCreator)
        self._plot_customizer.settingsChanged.connect(self.keySelected)

        self._central_tab = QTabWidget()
        self._central_tab.currentChanged.connect(self.currentPlotChanged)

        central_widget = QWidget()
        central_layout = QVBoxLayout()
        central_layout.setContentsMargins(0, 0, 0, 0)
        central_widget.setLayout(central_layout)

        central_layout.addWidget(self._central_tab)

        self.setCentralWidget(central_widget)

        self._plot_widgets = []
        """:type: list of PlotWidget"""

        self._data_gatherers = []
        """:type: list of PlotDataGatherer """

        summary_gatherer = self.createDataGatherer(PDG.gatherSummaryData, key_manager.isSummaryKey, refcaseGatherFunc=PDG.gatherSummaryRefcaseData, observationGatherFunc=PDG.gatherSummaryObservationData, historyGatherFunc=PDG.gatherSummaryHistoryData)
        gen_data_gatherer = self.createDataGatherer(PDG.gatherGenDataData, key_manager.isGenDataKey, observationGatherFunc=PDG.gatherGenDataObservationData)
        gen_kw_gatherer = self.createDataGatherer(PDG.gatherGenKwData, key_manager.isGenKwKey)
        custom_kw_gatherer = self.createDataGatherer(PDG.gatherCustomKwData, key_manager.isCustomKwKey)


        self.addPlotWidget(ENSEMBLE, plots.plotEnsemble, [summary_gatherer, gen_data_gatherer])
        self.addPlotWidget(STATISTICS, plots.plotStatistics, [summary_gatherer, gen_data_gatherer])
        self.addPlotWidget(HISTOGRAM, plots.plotHistogram, [gen_kw_gatherer, custom_kw_gatherer])
        self.addPlotWidget(GAUSSIAN_KDE, plots.plotGaussianKDE, [gen_kw_gatherer, custom_kw_gatherer])
        self.addPlotWidget(DISTRIBUTION, plots.plotDistribution, [gen_kw_gatherer, custom_kw_gatherer])
        self.addPlotWidget(CROSS_CASE_STATISTICS, plots.plotCrossCaseStatistics, [gen_kw_gatherer, custom_kw_gatherer])


        data_types_key_model = DataTypeKeysListModel(self._ert)

        self._data_type_keys_widget = DataTypeKeysWidget(data_types_key_model)
        self._data_type_keys_widget.dataTypeKeySelected.connect(self.keySelected)
        self.addDock("Data types", self._data_type_keys_widget)

        current_case = getCurrentCaseName()
        self._case_selection_widget = CaseSelectionWidget(current_case)
        self._case_selection_widget.caseSelectionChanged.connect(self.keySelected)
        self.addDock("Plot case", self._case_selection_widget)

        current_plot_widget = self._plot_widgets[self._central_tab.currentIndex()]
        current_plot_widget.setActive()
        self._data_type_keys_widget.selectDefault()
        self._updateCustomizer(current_plot_widget)




    def createDataGatherer(self, dataGatherFunc, gatherConditionFunc, refcaseGatherFunc=None, observationGatherFunc=None, historyGatherFunc=None):
        data_gatherer = PDG(dataGatherFunc, gatherConditionFunc, refcaseGatherFunc=refcaseGatherFunc, observationGatherFunc=observationGatherFunc, historyGatherFunc=historyGatherFunc)
        self._data_gatherers.append(data_gatherer)
        return data_gatherer


    def currentPlotChanged(self):
        for plot_widget in self._plot_widgets:
            plot_widget.setActive(False)
            index = self._central_tab.indexOf(plot_widget)

            if index == self._central_tab.currentIndex() and plot_widget.canPlotKey(self.getSelectedKey()):
                plot_widget.setActive()
                self._updateCustomizer(plot_widget)
                plot_widget.updatePlot()

    def _updateCustomizer(self, plot_widget):
        """ @type plot_widget: PlotWidget """
        key = self.getSelectedKey()
        key_manager = self._ert.getKeyManager()

        index_type = PlotContext.UNKNOWN_AXIS

        if key_manager.isGenDataKey(key):
            index_type = PlotContext.INDEX_AXIS
        elif key_manager.isSummaryKey(key):
            index_type = PlotContext.DATE_AXIS

        x_axis_type = PlotContext.UNKNOWN_AXIS
        y_axis_type = PlotContext.UNKNOWN_AXIS

        if plot_widget.name == ENSEMBLE:
            x_axis_type = index_type
            y_axis_type = PlotContext.VALUE_AXIS
        elif plot_widget.name == STATISTICS:
            x_axis_type = index_type
            y_axis_type = PlotContext.VALUE_AXIS
        elif plot_widget.name == DISTRIBUTION:
            y_axis_type = PlotContext.VALUE_AXIS
        elif plot_widget.name == CROSS_CASE_STATISTICS:
            y_axis_type = PlotContext.VALUE_AXIS
        elif plot_widget.name == HISTOGRAM:
            x_axis_type = PlotContext.VALUE_AXIS
            y_axis_type = PlotContext.COUNT_AXIS
        elif plot_widget.name == GAUSSIAN_KDE:
            x_axis_type = PlotContext.VALUE_AXIS
            y_axis_type = PlotContext.DENSITY_AXIS

        self._plot_customizer.setAxisTypes(x_axis_type, y_axis_type)


    def createPlotContext(self, figure):
        key = self.getSelectedKey()
        cases = self._case_selection_widget.getPlotCaseNames()
        data_gatherer = self.getDataGathererForKey(key)
        plot_config = PlotConfig.createCopy(self._plot_customizer.getPlotConfig())
        plot_config.setTitle(key)
        return PlotContext(self._ert, figure, plot_config, cases, key, data_gatherer)

    def getDataGathererForKey(self, key):
        """ @rtype: PlotDataGatherer """
        return next((data_gatherer for data_gatherer in self._data_gatherers if data_gatherer.canGatherDataForKey(key)), None)

    def getSelectedKey(self):
        return str(self._data_type_keys_widget.getSelectedItem())

    def addPlotWidget(self, name, plotFunction, data_gatherers, enabled=True):
        plot_condition_function_list = [data_gatherer.canGatherDataForKey for data_gatherer in data_gatherers]
        plot_widget = PlotWidget(name, plotFunction, plot_condition_function_list, self.createPlotContext)
        plot_widget.customizationTriggered.connect(self.toggleCustomizeDialog)

        index = self._central_tab.addTab(plot_widget, name)
        self._plot_widgets.append(plot_widget)
        self._central_tab.setTabEnabled(index, enabled)


    def addDock(self, name, widget, area=Qt.LeftDockWidgetArea, allowed_areas=Qt.AllDockWidgetAreas):
        dock_widget = QDockWidget(name)
        dock_widget.setObjectName("%sDock" % name)
        dock_widget.setWidget(widget)
        dock_widget.setAllowedAreas(allowed_areas)
        dock_widget.setFeatures(QDockWidget.DockWidgetFloatable | QDockWidget.DockWidgetMovable)

        self.addDockWidget(area, dock_widget)
        return dock_widget


    @showWaitCursorWhileWaiting
    def keySelected(self):
        key = self.getSelectedKey()
        self._plot_customizer.switchPlotConfigHistory(key)

        for plot_widget in self._plot_widgets:
            plot_widget.setDirty()
            index = self._central_tab.indexOf(plot_widget)
            self._central_tab.setTabEnabled(index, plot_widget.canPlotKey(key))

        for plot_widget in self._plot_widgets:
            if plot_widget.canPlotKey(key):
                plot_widget.updatePlot()


    def toggleCustomizeDialog(self):
        self._plot_customizer.toggleCustomizationDialog()