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)
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()
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()
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)
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()
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())
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)