Exemple #1
0
    def _build_ui(self):
        layout = QFormLayout()

        self.substituent_table = SubstituentTable(singleSelect=True)
        layout.addRow(self.substituent_table)

        self.new_residue = QCheckBox()
        self.new_residue.setCheckState(
            Qt.Checked if SubstituteMouseMode.newRes else Qt.Unchecked)
        layout.addRow("new residue:", self.new_residue)

        self.res_name = QLineEdit()
        self.res_name.setPlaceholderText("leave blank to keep current")
        layout.addRow("set residue name:", self.res_name)

        self.distance_names = QCheckBox()
        self.distance_names.setCheckState(
            Qt.Checked if SubstituteMouseMode.useRemoteness else Qt.Unchecked)
        layout.addRow("distance atom names:", self.distance_names)

        self.keep_open = QCheckBox()
        layout.addRow("keep list open:", self.keep_open)

        do_it = QPushButton("set substituent")
        do_it.clicked.connect(self.set_sub)
        layout.addRow(do_it)

        self.keep_open.stateChanged.connect(
            lambda state: do_it.setVisible(state != Qt.Checked))
        self.keep_open.stateChanged.connect(self.sub_changed)

        self.substituent_table.table.itemSelectionChanged.connect(
            self.sub_changed)
        self.new_residue.stateChanged.connect(self.sub_changed)
        self.res_name.textChanged.connect(self.sub_changed)
        self.distance_names.stateChanged.connect(self.sub_changed)

        self.tool_window.ui_area.setLayout(layout)

        self.tool_window.manage(None)
Exemple #2
0
class WindowDragger(QFrame, object):
    """
    Class to create custom window dragger for Solstice Tools
    """

    DEFAULT_LOGO_ICON_SIZE = 22

    doubleClicked = Signal()

    def __init__(self, window=None, on_close=None):
        super(WindowDragger, self).__init__(window)

        self._window = window
        self._dragging_enabled = True
        self._lock_window_operations = False
        self._mouse_press_pos = None
        self._mouse_move_pos = None
        self._dragging_threshold = 5
        self._minimize_enabled = True
        self._maximize_enabled = True
        self._on_close = on_close

        self.setObjectName('titleFrame')

        self.ui()

    # =================================================================================================================
    # PROPERTIES
    # =================================================================================================================

    @property
    def contents_layout(self):
        return self._contents_layout

    @property
    def corner_contents_layout(self):
        return self._corner_contents_layout

    # =================================================================================================================
    # OVERRIDES
    # =================================================================================================================

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton and self._dragging_enabled:
            self._mouse_press_pos = event.globalPos()
            self._mouse_move_pos = event.globalPos() - self._window.pos()
        super(WindowDragger, self).mousePressEvent(event)

    def mouseMoveEvent(self, event):
        if event.buttons() & Qt.LeftButton:
            global_pos = event.globalPos()
            if self._mouse_press_pos and self._dragging_enabled:
                moved = global_pos - self._mouse_press_pos
                if moved.manhattanLength() > self._dragging_threshold:
                    diff = global_pos - self._mouse_move_pos
                    self._window.move(diff)
                    self._mouse_move_pos = global_pos - self._window.pos()
        super(WindowDragger, self).mouseMoveEvent(event)

    def mouseDoubleClickEvent(self, event):
        if self._lock_window_operations:
            return
        if self._button_maximized.isVisible():
            self._on_maximize_window()
        else:
            self._on_restore_window()
        super(WindowDragger, self).mouseDoubleClickEvent(event)
        self.doubleClicked.emit()

    def mouseReleaseEvent(self, event):
        if self._mouse_press_pos is not None:
            if event.button() == Qt.LeftButton and self._dragging_enabled:
                moved = event.globalPos() - self._mouse_press_pos
                if moved.manhattanLength() > self._dragging_threshold:
                    event.ignore()
                self._mouse_press_pos = None
        super(WindowDragger, self).mouseReleaseEvent(event)

    # =================================================================================================================
    # BASE
    # =================================================================================================================

    def ui(self):
        self.setFixedHeight(qtutils.dpi_scale(40))

        main_layout = layouts.HorizontalLayout(spacing=5,
                                               margins=(15, 0, 15, 0))
        self.setLayout(main_layout)

        self._logo_button = self._setup_logo_button()
        self._title_text = label.ClippedLabel(text=self._window.windowTitle())
        self._title_text.setObjectName('WindowDraggerLabel')
        self._contents_layout = layouts.HorizontalLayout()
        self._corner_contents_layout = layouts.HorizontalLayout()

        main_layout.addWidget(self._logo_button)
        main_layout.addWidget(self._title_text)
        main_layout.addItem(
            QSpacerItem(25, 0, QSizePolicy.Fixed, QSizePolicy.Fixed))
        main_layout.addLayout(self._contents_layout)
        main_layout.addLayout(self._corner_contents_layout)

        buttons_widget = QWidget()
        self.buttons_layout = layouts.HorizontalLayout(spacing=0,
                                                       margins=(0, 0, 0, 0))
        self.buttons_layout.setAlignment(Qt.AlignRight)
        buttons_widget.setLayout(self.buttons_layout)
        main_layout.addWidget(buttons_widget)

        self._button_minimized = QPushButton()
        self._button_minimized.setIconSize(QSize(25, 25))
        # self._button_minimized.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed))
        self._button_minimized.setIcon(
            resources.icon('minimize', theme='window'))
        self._button_minimized.setStyleSheet(
            'QWidget {background-color: rgba(255, 255, 255, 0); border:0px;}')
        self._button_maximized = QPushButton()
        self._button_maximized.setIcon(
            resources.icon('maximize', theme='window'))
        # self._button_maximized.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed))
        self._button_maximized.setStyleSheet(
            'QWidget {background-color: rgba(255, 255, 255, 0); border:0px;}')
        self._button_maximized.setIconSize(QSize(25, 25))
        self._button_restored = QPushButton()
        # self._button_restored.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed))
        self._button_restored.setVisible(False)
        self._button_restored.setIcon(resources.icon('restore',
                                                     theme='window'))
        self._button_restored.setStyleSheet(
            'QWidget {background-color: rgba(255, 255, 255, 0); border:0px;}')
        self._button_restored.setIconSize(QSize(25, 25))
        self._button_closed = QPushButton()
        # button_closed.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed))
        self._button_closed.setIcon(resources.icon('close', theme='window'))
        self._button_closed.setStyleSheet(
            'QWidget {background-color: rgba(255, 255, 255, 0); border:0px;}')
        self._button_closed.setIconSize(QSize(25, 25))

        self.buttons_layout.addWidget(self._button_minimized)
        self.buttons_layout.addWidget(self._button_maximized)
        self.buttons_layout.addWidget(self._button_restored)
        self.buttons_layout.addWidget(self._button_closed)

        self._button_maximized.clicked.connect(self._on_maximize_window)
        self._button_minimized.clicked.connect(self._on_minimize_window)
        self._button_restored.clicked.connect(self._on_restore_window)
        self._button_closed.clicked.connect(self._on_close_window)

    def set_icon(self, icon=None, highlight=False):
        """
        Sets the icon of the window dragger
        :param icon: QIcon
        :param highlight: bool
        """

        icon = icon or self._window.windowIcon()
        if not icon or icon.isNull():
            icon = resources.icon('tpDcc')

        size = self.DEFAULT_LOGO_ICON_SIZE

        if highlight:
            self._logo_button.set_icon(
                [icon],
                colors=[None],
                tint_composition=QPainter.CompositionMode_Plus,
                size=size,
                icon_scaling=[1],
                color_offset=0,
                grayscale=True)
        else:
            self._logo_button.set_icon([icon],
                                       colors=None,
                                       size=size,
                                       icon_scaling=[1],
                                       color_offset=0)

        self._logo_button.set_icon_idle(icon)

        # self._lbl_icon.setPixmap(icon.pixmap(icon.actualSize(QSize(24, 24))))

    def set_height(self, value):
        """
        Sets the size of the dragger and updates icon
        :param value: float
        """

        self.setFixedHeight(qtutils.dpi_scale(value))

    def set_title(self, title):
        """
        Sets the title of the window dragger
        :param title: str
        """

        self._title_text.setText(title)

    def set_dragging_enabled(self, flag):
        """
        Sets whether or not drag functionality is enabled
        :param flag: bool
        """

        self._dragging_enabled = flag

    def set_minimize_enabled(self, flag):
        """
        Sets whether dragger shows minimize button or not
        :param flag: bool
        """

        self._minimize_enabled = flag
        self._button_minimized.setVisible(flag)

    def set_maximized_enabled(self, flag):
        """
        Sets whether dragger shows maximize button or not
        :param flag: bool
        """

        self._maximize_enabled = flag
        self._button_maximized.setVisible(flag)

    def show_logo(self):
        """
        Shows window logo
        """

        self._logo_button.setVisible(True)

    def hide_logo(self):
        """
        Hides window logo
        """

        self._logo_button.setVisible(False)

    def set_window_buttons_state(self, state, show_close_button=False):
        """
        Sets the state of the dragger buttons
        :param state: bool
        :param show_close_button: bool
        """

        self._lock_window_operations = not state
        self._button_closed.setEnabled(state or show_close_button)
        self._button_closed.setVisible(state or show_close_button)

        if self._maximize_enabled:
            self._button_maximized.setEnabled(state)
            self._button_maximized.setVisible(state)
        else:
            self._button_maximized.setEnabled(False)
            self._button_maximized.setVisible(False)

        if self._minimize_enabled:
            self._button_minimized.setEnabled(state)
            self._button_minimized.setVisible(state)
        else:
            self._button_minimized.setEnabled(False)
            self._button_minimized.setVisible(False)

        if not state:
            self._button_restored.setEnabled(state)
            self._button_restored.setVisible(state)
        else:
            if self.isMaximized():
                self._button_restored.setEnabled(state)
                self._button_restored.setVisible(state)

    def set_frameless_enabled(self, frameless=False):
        """
        Enables/Disables frameless mode or OS system default
        :param frameless: bool
        """

        from tpDcc.managers import tools

        tool_inst = tools.ToolsManager().get_tool_by_plugin_instance(
            self._window)
        if not tool_inst:
            return

        offset = QPoint()

        if self._window.docked():
            rect = self._window.rect()
            pos = self._window.mapToGlobal(QPoint(-10, -10))
            rect.setWidth(rect.width() + 21)
            self._window.close()
        else:
            rect = self.window().rect()
            pos = self.window().pos()
            offset = QPoint(3, 15)
            self.window().close()

        tool_inst._launch(launch_frameless=frameless)

        new_tool = tool_inst.latest_tool()

        QTimer.singleShot(
            0,
            lambda: new_tool.window().setGeometry(pos.x() + offset.x(),
                                                  pos.y() + offset.y(),
                                                  rect.width(), rect.height()))
        new_tool.framelessChanged.emit(frameless)
        QApplication.processEvents()

        return new_tool

    def _setup_logo_button(self):
        """
        Internal function that setup window dragger button logo
        :return: IconMenuButton
        """

        from tpDcc.libs.qt.widgets import buttons
        logo_button = buttons.IconMenuButton(parent=self)
        logo_button.setIconSize(QSize(24, 24))
        logo_button.setFixedSize(QSize(30, 30))
        self._toggle_frameless = logo_button.addAction(
            'Toggle Frameless Mode',
            connect=self._on_toggle_frameless_mode,
            checkable=True)
        self._toggle_frameless.setChecked(self._window.is_frameless())
        logo_button.set_menu_align(Qt.AlignLeft)

        return logo_button

    def _on_toggle_frameless_mode(self, action):
        """
        Internal callback function that is called when switch frameless mode button is pressed by user
        :param flag: bool
        """

        self.set_frameless_enabled(action.isChecked())

    def _on_maximize_window(self):
        """
        Internal callback function that is called when the user clicks on maximize button
        """

        self._button_restored.setVisible(True)
        self._button_maximized.setVisible(False)
        self._window.setWindowState(Qt.WindowMaximized)

    def _on_minimize_window(self):
        """
        Internal callback function that is called when the user clicks on minimize button
        """

        self._window.setWindowState(Qt.WindowMinimized)

    def _on_restore_window(self):
        """
        Internal callback function that is called when the user clicks on restore button
        """

        self._button_restored.setVisible(False)
        self._button_maximized.setVisible(True)
        self._window.setWindowState(Qt.WindowNoState)

    def _on_close_window(self):
        """
        Internal callback function that is called when the user clicks on close button
        """

        from tpDcc.managers import tools

        closed = False
        if hasattr(self._window, 'WindowId'):
            closed = tools.ToolsManager().close_tool(self._window.WindowId,
                                                     force=False)

        if not closed:
            if hasattr(self._window, 'docked'):
                if self._window.docked():
                    self._window.fade_close()
                else:
                    self.window().fade_close()
            else:
                self._window.fade_close()
Exemple #3
0
class WindowStatusBar(statusbar.StatusWidget, object):
    def __init__(self, parent=None):
        super(WindowStatusBar, self).__init__(parent)

        self._info_url = None
        self._tool = None

        self.setFixedHeight(25)
        self._info_btn = QPushButton()
        self._info_btn.setIconSize(QSize(25, 25))
        self._info_btn.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed))
        self._info_btn.setIcon(resources.icon('info1'))
        self._info_btn.setStyleSheet('QWidget {background-color: rgba(255, 255, 255, 0); border:0px;}')

        self._bug_btn = QPushButton()
        self._bug_btn.setIconSize(QSize(25, 25))
        self._bug_btn.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
        self._bug_btn.setIcon(resources.icon('bug'))
        self._bug_btn.setStyleSheet('QWidget {background-color: rgba(255, 255, 255, 0); border:0px;}')

        self.main_layout.insertWidget(0, self._info_btn)
        self.main_layout.insertWidget(1, self._bug_btn)

        self._info_btn.clicked.connect(self._on_open_url)
        self._bug_btn.clicked.connect(self._on_send_bug)

    def set_info_url(self, url):
        """
        Sets the URL used to open tool info documentation web
        :param url: str
        """

        self._info_url = url

    def set_tool(self, tool):
        """

        :param tool:
        :return:
        """

        self._tool = tool

    def has_url(self):
        """
        Returns whether the URL documentation web is set or not
        :return: bool
        """

        if self._info_url:
            return True

        return False

    def has_tool(self):
        """
        Returns whether window has a tool attached or not
        :return: bool
        """

        if self._tool:
            return True

        return False

    def show_info(self):
        """
        Shows the info button of the status bar
        """

        self._info_btn.setVisible(True)

    def hide_info(self):
        """
        Hides the info button of the status bar
        """

        self._info_btn.setVisible(False)

    def show_bug(self):
        self._bug_btn.setVisible(True)

    def hide_bug(self):
        self._bug_btn.setVisible(False)

    def open_info_url(self):
        """
        Opens tool documentation URL in user web browser
        """

        if not self._project:
            return False

        if self._info_url:
            webbrowser.open_new_tab(self._info_url)

    def _on_send_bug(self):

        if not self._project:
            return False

        # tools.ToolsManager().run_tool(self._project, 'bugtracker', extra_args={'tool': self._tool})

    def _on_open_url(self):
        """
        Internal callback function that is called when the user presses the info icon button
        :return:
        """

        self.open_info_url()
Exemple #4
0
    def _build_ui(self):
        layout = QFormLayout()

        initial_elements = []
        if ChangeElementMouseMode.element:
            initial_elements = [ChangeElementMouseMode.element]
        else:
            initial_elements = ["C"]

        self.periodic_table = PeriodicTable(select_multiple=False,
                                            initial_elements=initial_elements)
        self.periodic_table.elementSelectionChanged.connect(
            self.element_changed)
        layout.addRow(self.periodic_table)

        self.vsepr = QComboBox()
        self.vsepr.addItems([
            "do not change",  # 0
            "linear (1 bond)",  # 1
            "linear (2 bonds)",  # 2 
            "trigonal planar (2 bonds)",  # 3
            "tetrahedral (2 bonds)",  # 4 
            "trigonal planar",  # 5
            "tetrahedral (3 bonds)",  # 6
            "T-shaped",  # 7
            "trigonal pyramidal",  # 8
            "tetrahedral",  # 9
            "sawhorse",  #10
            "seesaw",  #11
            "square planar",  #12
            "trigonal bipyramidal",  #13
            "square pyramidal",  #14
            "pentagonal",  #15
            "octahedral",  #16
            "hexagonal",  #17
            "trigonal prismatic",  #18
            "pentagonal pyramidal",  #19
            "capped octahedral",  #20
            "capped trigonal prismatic",  #21
            "heptagonal",  #22
            "hexagonal pyramidal",  #23
            "pentagonal bipyramidal",  #24
            "biaugmented trigonal prismatic",  #25
            "cubic",  #26
            "elongated trigonal bipyramidal",  #27
            "hexagonal bipyramidal",  #28
            "heptagonal pyramidal",  #29
            "octagonal",  #30
            "square antiprismatic",  #31
            "trigonal dodecahedral",  #32
            "capped cube",  #33
            "capped square antiprismatic",  #34
            "enneagonal",  #35
            "heptagonal bipyramidal",  #36
            "hula-hoop",  #37
            "triangular cupola",  #38
            "tridiminished icosahedral",  #39
            "muffin",  #40
            "octagonal pyramidal",  #41
            "tricapped trigonal prismatic",  #42
        ])
        self.vsepr.setCurrentIndex(9)

        self.vsepr.insertSeparator(33)
        self.vsepr.insertSeparator(25)
        self.vsepr.insertSeparator(20)
        self.vsepr.insertSeparator(16)
        self.vsepr.insertSeparator(13)
        self.vsepr.insertSeparator(8)
        self.vsepr.insertSeparator(5)
        self.vsepr.insertSeparator(2)
        self.vsepr.insertSeparator(1)
        self.vsepr.insertSeparator(0)
        self.vsepr.currentTextChanged.connect(self.element_changed)
        layout.addRow("shape:", self.vsepr)

        self.keep_open = QCheckBox()
        layout.addRow("keep table open:", self.keep_open)

        if ChangeElementMouseMode.vsepr:
            ndx = self.vsepr.findText(ChangeElementMouseMode.vsepr,
                                      Qt.MatchExactly)
            if ndx != -1:
                self.vsepr.setCurrentIndex(ndx)

        do_it = QPushButton("set element and shape")
        do_it.clicked.connect(self.set_selected)
        layout.addRow(do_it)

        self.keep_open.stateChanged.connect(
            lambda state: do_it.setVisible(state != Qt.Checked))
        self.keep_open.stateChanged.connect(self.element_changed)

        self.tool_window.ui_area.setLayout(layout)

        self.tool_window.manage(None)
Exemple #5
0
class PrecisionRotate(ToolInstance):

    help = "https://github.com/QChASM/SEQCROW/wiki/Rotate-Tool"
    SESSION_ENDURING = False
    SESSION_SAVE = False

    def __init__(self, session, name):
        super().__init__(session, name)

        self.tool_window = MainToolWindow(self)

        self.settings = _PrecisionRotateSettings(session, name)

        self.bonds = {}
        self.bond_centers = {}
        self.groups = {}
        self.perpendiculars = {}
        self.perp_centers = {}
        self.manual_center = {}

        self._build_ui()

        self._show_rot_vec = self.session.triggers.add_handler(
            SELECTION_CHANGED, self.show_rot_vec)
        global_triggers = get_triggers()
        self._changes = global_triggers.add_handler("changes done",
                                                    self.show_rot_vec)

        self.show_rot_vec()

    def _build_ui(self):
        layout = QGridLayout()

        layout.addWidget(QLabel("center of rotation:"), 0, 0, 1, 1,
                         Qt.AlignLeft | Qt.AlignVCenter)

        self.cor_button = QComboBox()
        self.cor_button.addItems(
            ["automatic", "select atoms", "view's center of rotation"])
        layout.addWidget(self.cor_button, 0, 1, 1, 1, Qt.AlignTop)

        self.set_cor_selection = QPushButton("set selection")
        self.cor_button.currentTextChanged.connect(
            lambda t, widget=self.set_cor_selection: widget.setEnabled(
                t == "select atoms"))
        self.set_cor_selection.clicked.connect(self.manual_cor)
        layout.addWidget(self.set_cor_selection, 0, 2, 1, 1, Qt.AlignTop)
        self.set_cor_selection.setEnabled(False)

        layout.addWidget(QLabel("rotation vector:"), 1, 0, 1, 1,
                         Qt.AlignLeft | Qt.AlignVCenter)

        self.vector_option = QComboBox()
        self.vector_option.addItems([
            "axis", "view axis", "bond", "perpendicular to plane",
            "centroid of atoms", "custom"
        ])
        layout.addWidget(self.vector_option, 1, 1, 1, 1, Qt.AlignVCenter)

        vector = QWidget()
        vector.setToolTip("vector will be normalized before rotating")
        vector_layout = QHBoxLayout(vector)
        vector_layout.setContentsMargins(0, 0, 0, 0)
        self.vector_x = QDoubleSpinBox()
        self.vector_y = QDoubleSpinBox()
        self.vector_z = QDoubleSpinBox()
        self.vector_z.setValue(1.0)
        for c, t in zip([self.vector_x, self.vector_y, self.vector_z],
                        [" x", " y", " z"]):
            c.setSingleStep(0.01)
            c.setRange(-100, 100)
            # c.setSuffix(t)
            c.valueChanged.connect(self.show_rot_vec)
            vector_layout.addWidget(c)

        layout.addWidget(vector, 1, 2, 1, 1, Qt.AlignTop)
        vector.setVisible(self.vector_option.currentText() == "custom")
        self.vector_option.currentTextChanged.connect(
            lambda text, widget=vector: widget.setVisible(text == "custom"))

        self.view_axis = QComboBox()
        self.view_axis.addItems(["z", "y", "x"])
        layout.addWidget(self.view_axis, 1, 2, 1, 1, Qt.AlignTop)
        self.view_axis.setVisible(
            self.vector_option.currentText() == "view axis")
        self.vector_option.currentTextChanged.connect(
            lambda text, widget=self.view_axis: widget.setVisible(text ==
                                                                  "view axis"))

        self.axis = QComboBox()
        self.axis.addItems(["z", "y", "x"])
        layout.addWidget(self.axis, 1, 2, 1, 1, Qt.AlignTop)
        self.axis.setVisible(self.vector_option.currentText() == "axis")
        self.vector_option.currentTextChanged.connect(
            lambda text, widget=self.axis: widget.setVisible(text == "axis"))

        self.bond_button = QPushButton("set selected bond")
        self.bond_button.clicked.connect(self.set_bonds)
        layout.addWidget(self.bond_button, 1, 2, 1, 1, Qt.AlignTop)
        self.bond_button.setVisible(self.vector_option.currentText() == "bond")
        self.vector_option.currentTextChanged.connect(
            lambda text, widget=self.bond_button: widget.setVisible(text ==
                                                                    "bond"))

        self.perp_button = QPushButton("set selected atoms")
        self.perp_button.clicked.connect(self.set_perpendicular)
        layout.addWidget(self.perp_button, 1, 2, 1, 1, Qt.AlignTop)
        self.perp_button.setVisible(
            self.vector_option.currentText() == "perpendicular to plane")
        self.vector_option.currentTextChanged.connect(
            lambda text, widget=self.perp_button: widget.setVisible(
                text == "perpendicular to plane"))

        self.group_button = QPushButton("set selected atoms")
        self.group_button.clicked.connect(self.set_group)
        layout.addWidget(self.group_button, 1, 2, 1, 1, Qt.AlignTop)
        self.group_button.setVisible(
            self.vector_option.currentText() == "centroid of atoms")
        self.vector_option.currentTextChanged.connect(
            lambda text, widget=self.group_button: widget.setVisible(
                text == "centroid of atoms"))

        layout.addWidget(QLabel("angle:"), 2, 0, 1, 1,
                         Qt.AlignLeft | Qt.AlignVCenter)

        self.angle = QDoubleSpinBox()
        self.angle.setRange(-360, 360)
        self.angle.setSingleStep(5)
        self.angle.setSuffix("°")
        layout.addWidget(self.angle, 2, 1, 1, 1,
                         Qt.AlignLeft | Qt.AlignVCenter)

        layout.addWidget(QLabel("preview rotation axis:"), 3, 0, 1, 1,
                         Qt.AlignLeft | Qt.AlignVCenter)
        self.display_rot_vec = QCheckBox()
        self.display_rot_vec.setCheckState(Qt.Checked)
        self.display_rot_vec.stateChanged.connect(self.show_rot_vec)
        layout.addWidget(self.display_rot_vec, 3, 1, 1, 1,
                         Qt.AlignLeft | Qt.AlignVCenter)

        rotate_button = QPushButton("rotate selected atoms")
        rotate_button.clicked.connect(self.do_rotate)
        layout.addWidget(rotate_button, 4, 0, 1, 3, Qt.AlignTop)
        self.rotate_button = rotate_button

        self.status_bar = QStatusBar()
        self.status_bar.setSizeGripEnabled(False)
        layout.addWidget(self.status_bar, 5, 0, 1, 3, Qt.AlignTop)

        self.vector_option.currentTextChanged.connect(self.show_auto_status)
        self.cor_button.currentIndexChanged.connect(
            lambda *args: self.show_auto_status("select atoms"))

        self.cor_button.currentIndexChanged.connect(self.show_rot_vec)
        self.set_cor_selection.clicked.connect(self.show_rot_vec)
        self.vector_option.currentIndexChanged.connect(self.show_rot_vec)
        self.axis.currentIndexChanged.connect(self.show_rot_vec)
        self.view_axis.currentIndexChanged.connect(self.show_rot_vec)
        self.bond_button.clicked.connect(self.show_rot_vec)
        self.perp_button.clicked.connect(self.show_rot_vec)
        self.group_button.clicked.connect(self.show_rot_vec)

        layout.setRowStretch(0, 0)
        layout.setRowStretch(1, 0)
        layout.setRowStretch(2, 0)
        layout.setRowStretch(3, 0)
        layout.setRowStretch(4, 0)
        layout.setRowStretch(5, 1)

        layout.setColumnStretch(0, 0)
        layout.setColumnStretch(1, 1)
        layout.setColumnStretch(2, 1)

        self.tool_window.ui_area.setLayout(layout)

        self.tool_window.manage(None)

    def manual_cor(self, *args):
        selection = selected_atoms(self.session)

        models = {}
        for atom in selection:
            if atom.structure not in models:
                models[atom.structure] = [atom]
            else:
                models[atom.structure].append(atom)

        self.manual_center = {}
        for model in models:
            atoms = models[model]
            coords = np.array([atom.coord for atom in atoms])
            self.manual_center[model] = np.mean(coords, axis=0)

    def show_auto_status(self, text):
        if self.cor_button.currentText() == "automatic":
            if text == "bond":
                self.status_bar.showMessage(
                    "center set to one of the bonded atoms")
            elif text == "perpendicular to plane":
                self.status_bar.showMessage("center set to centroid of atoms")
            else:
                self.status_bar.showMessage(
                    "center set to centroid of rotating atoms")

        elif self.cor_button.currentText() == "select atoms":
            self.status_bar.showMessage(
                "center set to centroid of specified atoms")

        else:
            self.status_bar.showMessage(
                "center set to view's center of rotation")

    def set_bonds(self, *args):
        bonds = selected_bonds(self.session)
        if len(bonds) == 0:
            self.session.logger.error("no bonds selected")
            return

        models = [bond.structure for bond in bonds]
        if any(models.count(m) > 1 for m in models):
            self.session.logger.error(
                "multiple bonds selected on the same structure")
            return

        self.bonds = {
            model: (bond.atoms[0].coord - bond.atoms[1].coord)
            for model, bond in zip(models, bonds)
        }
        self.bond_centers = {
            model: bond.atoms[1].coord
            for model, bond in zip(models, bonds)
        }

    def set_perpendicular(self, *args):
        atoms = selected_atoms(self.session)
        if len(atoms) == 0:
            self.session.logger.error("no atoms selected")
            return

        self.perpendiculars = {}
        self.perp_centers = {}

        models = set(atom.structure for atom in atoms)
        for model in models:
            atom_coords = []
            for atom in atoms:
                if atom.structure is model:
                    atom_coords.append(atom.coord)

            if len(atom_coords) < 3:
                self.session.logger.error("fewer than 3 atoms selected on %s" %
                                          model.atomspec)
                continue

            xyz = np.array(atom_coords)
            xyz -= np.mean(atom_coords, axis=0)
            R = np.dot(xyz.T, xyz)
            u, s, vh = np.linalg.svd(R, compute_uv=True)
            vector = u[:, -1]

            self.perpendiculars[model] = vector
            self.perp_centers[model] = np.mean(atom_coords, axis=0)

    def set_group(self, *args):
        atoms = selected_atoms(self.session)
        if len(atoms) == 0:
            self.session.logger.error("no atoms selected")
            return

        self.groups = {}

        models = set(atom.structure for atom in atoms)
        for model in models:
            atom_coords = []
            for atom in atoms:
                if atom.structure is model:
                    atom_coords.append(atom.coord)

            self.groups[model] = np.mean(atom_coords, axis=0)

    def do_rotate(self, *args):
        selection = selected_atoms(self.session)

        models = {}
        for atom in selection:
            if atom.structure not in models:
                models[atom.structure] = [atom]
            else:
                models[atom.structure].append(atom)

        if len(models.keys()) == 0:
            return

        if self.vector_option.currentText() == "axis":
            if self.axis.currentText() == "z":
                vector = np.array([0., 0., 1.])
            elif self.axis.currentText() == "y":
                vector = np.array([0., 1., 0.])
            elif self.axis.currentText() == "x":
                vector = np.array([1., 0., 0.])

        elif self.vector_option.currentText() == "view axis":
            if self.view_axis.currentText() == "z":
                vector = self.session.view.camera.get_position().axes()[2]
            elif self.view_axis.currentText() == "y":
                vector = self.session.view.camera.get_position().axes()[1]
            elif self.view_axis.currentText() == "x":
                vector = self.session.view.camera.get_position().axes()[0]

        elif self.vector_option.currentText() == "bond":
            vector = self.bonds

        elif self.vector_option.currentText() == "perpendicular to plane":
            vector = self.perpendiculars

        elif self.vector_option.currentText() == "centroid of atoms":
            vector = self.groups

        elif self.vector_option.currentText() == "custom":
            x = self.vector_x.value()
            y = self.vector_y.value()
            z = self.vector_z.value()
            vector = np.array([x, y, z])

        angle = np.deg2rad(self.angle.value())

        center = {}
        for model in models:
            atoms = models[model]
            coords = np.array([atom.coord for atom in atoms])
            center[model] = np.mean(coords, axis=0)

        if self.cor_button.currentText() == "automatic":
            if self.vector_option.currentText() == "perpendicular to plane":
                center = self.perp_centers

            elif self.vector_option.currentText() == "bond":
                center = self.bond_centers

        elif self.cor_button.currentText() == "select atoms":
            center = self.manual_center

        else:
            center = self.session.main_view.center_of_rotation

        for model in models:
            if isinstance(vector, dict):
                if model not in vector.keys():
                    continue
                else:
                    v = vector[model]

            else:
                v = vector

            if isinstance(center, dict):
                if model not in center.keys():
                    continue
                else:
                    c = center[model]

            else:
                c = center

            if self.vector_option.currentText(
            ) == "centroid of atoms" and self.cor_button.currentText(
            ) != "automatic":
                v = v - c

            v = v / np.linalg.norm(v)
            q = np.hstack(([np.cos(angle / 2)], v * np.sin(angle / 2)))

            q /= np.linalg.norm(q)
            qs = q[0]
            qv = q[1:]

            xyz = np.array([a.coord for a in models[model]])
            xyz -= c
            xprod = np.cross(qv, xyz)
            qs_xprod = 2 * qs * xprod
            qv_xprod = 2 * np.cross(qv, xprod)

            xyz += qs_xprod + qv_xprod + c
            for t, coord in zip(models[model], xyz):
                t.coord = coord

    def show_rot_vec(self, *args):
        for model in self.session.models.list(type=Generic3DModel):
            if model.name == "rotation vector":
                model.delete()

        if self.display_rot_vec.checkState() == Qt.Unchecked:
            return

        selection = selected_atoms(self.session)

        if len(selection) == 0:
            return

        models = {}
        for atom in selection:
            if atom.structure not in models:
                models[atom.structure] = [atom]
            else:
                models[atom.structure].append(atom)

        if len(models.keys()) == 0:
            return

        if self.vector_option.currentText() == "axis":
            if self.axis.currentText() == "z":
                vector = np.array([0., 0., 1.])
            elif self.axis.currentText() == "y":
                vector = np.array([0., 1., 0.])
            elif self.axis.currentText() == "x":
                vector = np.array([1., 0., 0.])

        elif self.vector_option.currentText() == "view axis":
            if self.view_axis.currentText() == "z":
                vector = self.session.view.camera.get_position().axes()[2]
            elif self.view_axis.currentText() == "y":
                vector = self.session.view.camera.get_position().axes()[1]
            elif self.view_axis.currentText() == "x":
                vector = self.session.view.camera.get_position().axes()[0]

        elif self.vector_option.currentText() == "bond":
            vector = self.bonds

        elif self.vector_option.currentText() == "perpendicular to plane":
            vector = self.perpendiculars

        elif self.vector_option.currentText() == "centroid of atoms":
            vector = self.groups

        elif self.vector_option.currentText() == "custom":
            x = self.vector_x.value()
            y = self.vector_y.value()
            z = self.vector_z.value()
            vector = np.array([x, y, z])

        center = {}
        for model in models:
            atoms = models[model]
            coords = np.array([atom.coord for atom in atoms])
            center[model] = np.mean(coords, axis=0)

        if self.cor_button.currentText() == "automatic":
            if self.vector_option.currentText() == "perpendicular to plane":
                center = self.perp_centers

            elif self.vector_option.currentText() == "bond":
                center = self.bond_centers

        elif self.cor_button.currentText() == "select atoms":
            center = self.manual_center

        else:
            center = self.session.main_view.center_of_rotation

        for model in models:
            if isinstance(vector, dict):
                if model not in vector.keys():
                    continue
                else:
                    v = vector[model]

            else:
                v = vector

            if isinstance(center, dict):
                if model not in center.keys():
                    continue
                else:
                    c = center[model]

            else:
                c = center

            if self.vector_option.currentText(
            ) == "centroid of atoms" and self.cor_button.currentText(
            ) != "automatic":
                v = v - c

            if np.linalg.norm(v) == 0:
                continue

            residues = []
            for atom in models[model]:
                if atom.residue not in residues:
                    residues.append(atom.residue)

            v_c = c + v

            s = ".color red\n"
            s += ".arrow %10.6f %10.6f %10.6f   %10.6f %10.6f %10.6f   0.2 0.4 0.7\n" % (
                *c, *v_c)

            stream = BytesIO(bytes(s, 'utf-8'))
            bild_obj, status = read_bild(self.session, stream,
                                         "rotation vector")

            self.session.models.add(bild_obj, parent=model)

    def delete(self):
        self.session.triggers.remove_handler(self._show_rot_vec)
        global_triggers = get_triggers()
        global_triggers.remove_handler(self._changes)

        for model in self.session.models.list(type=Generic3DModel):
            if model.name == "rotation vector":
                model.delete()

        return super().delete()

    def close(self):
        self.session.triggers.remove_handler(self._show_rot_vec)
        global_triggers = get_triggers()
        global_triggers.remove_handler(self._changes)

        for model in self.session.models.list(type=Generic3DModel):
            if model.name == "rotation vector":
                model.delete()

        return super().close()
Exemple #6
0
class WidgetItem(QFrame, object):

    closed = Signal(QWidget)
    deleted = Signal(QWidget)

    def __init__(self,
                 item_height=150,
                 item_width=300,
                 height_offset=10,
                 width_offset=10,
                 has_title=True,
                 editable_title=True,
                 is_closable=True,
                 parent=None):
        super(WidgetItem, self).__init__(parent=parent)

        self._item_height = item_height
        self._item_width = item_width
        self._height_offset = height_offset
        self._width_offset = width_offset
        self._has_title = has_title
        self._editable_title = editable_title
        self._is_closable = is_closable
        self._animation = None

        self.setFrameStyle(QFrame.Panel | QFrame.Raised)

        self.ui()

    def ui(self):

        self.setLayout(layouts.VerticalLayout(spacing=0, margins=(3, 1, 3, 3)))

        self.main_layout = layouts.VerticalLayout(spacing=5,
                                                  margins=(2, 2, 2, 2))
        main_widget = QWidget()

        if self._item_height:
            main_widget.setFixedHeight(self._item_height - self._height_offset)
        if self._item_width:
            main_widget.setFixedWidth(self._item_width - self._width_offset)

        main_widget.setLayout(self.main_layout)
        self.layout().addWidget(main_widget)

        # =====================================================

        # This layout is used to add custom widgets before the title of the node
        self.buttons_layout = layouts.VerticalLayout(spacing=2,
                                                     margins=(2, 2, 2, 2))

        title_layout = layouts.HorizontalLayout()
        title_layout.addLayout(self.buttons_layout)

        title_line = QLineEdit('Untitled')
        if not self._editable_title:
            title_line.setEnabled(False)

        if self._has_title:
            title_layout.addWidget(title_line)

        self._close_btn = QPushButton('X')
        self._close_btn.setFixedHeight(20)
        self._close_btn.setFixedWidth(20)
        self._close_btn.clicked.connect(self.close_widget)
        if self._is_closable:
            title_layout.addWidget(self._close_btn)

        self.main_layout.addLayout(title_layout)

    def hide_close_button(self, value=True):
        self._close_btn.setVisible(not (value))

    def close_widget(self):
        if self._is_closable:
            self.closed.emit(self)

    def delete_widget(self):
        self.deleted.emit(self)

    def _animate_expand(self, value):

        size_anim = QPropertyAnimation(self, 'geometry')
        geometry = self.geometry()
        width = geometry.width()
        x, y, _, _ = geometry.getCoords()
        size_start = QRect(x, y, width, int(not (value)) * 150)
        size_end = QRect(x, y, width, value * 150)
        size_anim.setStartValue(size_start)
        size_anim.setEndValue(size_end)
        size_anim.setDuration(300)
        size_anim_curve = QEasingCurve()
        if value:
            size_anim_curve.setType(QEasingCurve.InQuad)
        else:
            size_anim_curve.setType(QEasingCurve.OutQuad)
        size_anim.setEasingCurve(size_anim_curve)

        # =================================================== Animation Sequence

        self._animation = QSequentialAnimationGroup()
        self._animation.addAnimation(size_anim)
        size_anim.valueChanged.connect(self._force_resize)
        if not value:
            self._animation.finished.connect(self.delete_widget)
        self._animation.start(QAbstractAnimation.DeleteWhenStopped)

    def _force_resize(self, new_height):

        # Force widget item parent to reevaluate its size
        self.setFixedHeight(new_height.height())
Exemple #7
0
class Badge(base.BaseWidget, object):
    """
    Widget that can be located near notification or user avatars to display unread messages count
    We support 3 types of styles:
        1. dof: show a dot
        2. count: show a number
        3. text: show a string
    """
    def __init__(self, widget=None, parent=None):

        self._dot = None
        self._text = None
        self._count = None
        self._widget = widget
        self._overflow_count = 99

        super(Badge, self).__init__(parent=parent)

    # =================================================================================================================
    # PROPERTIES
    # =================================================================================================================

    @property
    def overflow(self):
        """
        Returns current overflow number
        :return: int
        """

        return self._overflow_count

    @overflow.setter
    def overflow(self, value):
        """
        Sets overflow number
        :param value: int
        """

        self._overflow_count = value
        self._update_number()

    @property
    def count(self):
        """
        Returns current badge count number
        :return: int
        """

        return self._count

    @count.setter
    def count(self, value):
        """
        Sets current badge count number
        :param value: int
        """

        self._count = value
        self._update_number()

    @property
    def text(self):
        """
        Returns current badge text
        :return: str
        """

        return self._text

    @text.setter
    def text(self, value):
        """
        Sets current badge text
        :param value: str
        """

        self._text = value
        self._badge_btn.setText(self._text)
        self._badge_btn.setVisible(bool(self._text))
        self._dot = None
        self.style().polish(self)

    def _get_dot(self):
        """
        Returns whether or not current badge style is dot
        :return: bool
        """

        return self._dot

    def _set_dot(self, flag):
        """
        Sets whether or not current badge style is dot
        :param flag: bool
        """

        self._dot = flag
        self._badge_btn.setText('')
        self._badge_btn.setVisible(flag)
        self.style().polish(self)

    dot = Property(bool, _get_dot, _set_dot)

    # =================================================================================================================
    # OVERRIDES
    # =================================================================================================================

    def get_main_layout(self):
        main_layout = layouts.GridLayout(margins=(0, 0, 0, 0))

        return main_layout

    def ui(self):
        super(Badge, self).ui()

        self._badge_btn = QPushButton()
        self._badge_btn.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)

        if self._widget is not None:
            self.main_layout.addWidget(self._widget, 0, 0)
        self.main_layout.addWidget(self._badge_btn, 0, 0,
                                   Qt.AlignTop | Qt.AlignRight)

    # =================================================================================================================
    # BASE
    # =================================================================================================================

    @classmethod
    def create_dot(cls, show=False, widget=None, parent=None):
        """
        Creates a new badge with dot style
        :param show: bool
        :param widget: QWidget
        :param parent: QWidget
        :return: Badge
        """

        inst = cls(widget=widget, parent=parent)
        inst.dot = show

        return inst

    @classmethod
    def create_count(cls, count=0, widget=None, parent=None):
        """
        Creates a new badge with count style
        :param count: int
        :param widget: QWidget
        :param parent: QWidget
        :return: Badge
        """

        inst = cls(widget=widget, parent=parent)
        inst.count = count

        return inst

    @classmethod
    def create_text(cls, text='', widget=None, parent=None):
        """
        Creates a new badge with dot style
        :param text: str
        :param widget: QWidget
        :param parent: QWidget
        :return: Badge
        """

        inst = cls(widget=widget, parent=parent)
        inst.text = text

        return inst

    # =================================================================================================================
    # INTERNAL
    # =================================================================================================================

    def _update_number(self):
        """
        Internal function that updates overflow number
        """

        self._badge_btn.setText(
            formatters.overflow_format(self._count, self._overflow_count))
        self._badge_btn.setVisible(self._count > 0)
        self._dot = None
        self.style().polish(self)