def __init__(self, session, tool_name): # 'session' - chimerax.core.session.Session instance # 'tool_name' - string # Initialize base class. super().__init__(session, tool_name) # Set name displayed on title bar (defaults to tool_name) # Must be after the superclass init, which would override it. self.display_name = "HKLviewer" # Create the main window for our tool. The window object will have # a 'ui_area' where we place the widgets composing our interface. # The window isn't shown until we call its 'manage' method. # # Note that by default, tool windows are only hidden rather than # destroyed when the user clicks the window's close button. To change # this behavior, specify 'close_destroys=True' in the MainToolWindow # constructor. from chimerax.ui import MainToolWindow self.tool_window = MainToolWindow(self) # We will be adding an item to the tool's context menu, so override # the default MainToolWindow fill_context_menu method self.tool_window.fill_context_menu = self.fill_context_menu # Our user interface is simple enough that we could probably inline # the code right here, but for any kind of even moderately complex # interface, it is probably better to put the code in a method so # that this __init__ method remains readable. self._build_ui() self.clipper_crystdict = None session.HKLviewer = self
def __init__(self, session, tool_name, *, title=None): ToolInstance.__init__(self, session, tool_name) from chimerax.ui import MainToolWindow tw = MainToolWindow(self) if title is not None: tw.title = title self.tool_window = tw parent = tw.ui_area from matplotlib import figure self.figure = f = figure.Figure(dpi=100, figsize=(2, 2)) from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as Canvas self.canvas = c = Canvas(f) parent.setMinimumHeight( 1 ) # Matplotlib gives divide by zero error when plot resized to 0 height. c.setParent(parent) from PyQt5.QtWidgets import QHBoxLayout layout = QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(c) parent.setLayout(layout) tw.manage(placement="side") self.axes = axes = f.gca() self._pan = None # Pan/zoom mouse control
def __init__(self, session): ToolInstance.__init__(self, session, "Bogus Toolshed-demo Tool") self.display_name = "Open Models" from chimerax.ui import MainToolWindow self.tool_window = MainToolWindow(self, size=self.SIZE) parent = self.tool_window.ui_area # UI content code from wx import html2 import wx self.webview = html2.WebView.New(parent, wx.ID_ANY, size=self.SIZE) self.webview.Bind(html2.EVT_WEBVIEW_NAVIGATING, self._on_navigating, id=self.webview.GetId()) sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(self.webview, 1, wx.EXPAND) parent.SetSizerAndFit(sizer) self.tool_window.manage(placement="side") # Add triggers for model addition/removal from chimerax.core.models import ADD_MODELS, REMOVE_MODELS self._handlers = [ session.triggers.add_handler(ADD_MODELS, self._make_page), session.triggers.add_handler(REMOVE_MODELS, self._make_page) ] self._make_page()
def __init__(self, session, tool_name): ToolInstance.__init__(self, session, tool_name) # 'display_name' defaults to class name with spaces inserted # between lower-then-upper-case characters (therefore "Tool UI" # in this case), so only override if different name desired self.display_name = "ChimeraX Python Shell" from chimerax.ui import MainToolWindow self.tool_window = MainToolWindow(self) parent = self.tool_window.ui_area # UI content code from ipykernel.ipkernel import IPythonKernel save_ns = IPythonKernel.user_ns IPythonKernel.user_ns = {'session': session} from qtconsole.inprocess import QtInProcessKernelManager kernel_manager = QtInProcessKernelManager() kernel_manager.start_kernel() kernel_client = kernel_manager.client() kernel_client.start_channels() from qtconsole.rich_jupyter_widget import RichJupyterWidget self.shell = RichJupyterWidget(parent) def_banner = self.shell.banner self.shell.banner = "{}\nCurrent ChimeraX session available as 'session'.\n\n".format( def_banner) self.shell.kernel_manager = kernel_manager self.shell.kernel_client = kernel_client IPythonKernel.user_ns = save_ns from PyQt5.QtWidgets import QHBoxLayout layout = QHBoxLayout() layout.addWidget(self.shell) layout.setStretchFactor(self.shell, 1) parent.setLayout(layout) self.tool_window.manage(placement=None)
def __init__(self, session, name): super().__init__(session, name) from chimerax.ui import MainToolWindow self.tool_window = MainToolWindow(self) self.settings = _SterimolSettings(self.session, name) self._build_ui()
def __init__(self, session, name): super().__init__(session, name) from chimerax.ui import MainToolWindow self.tool_window = MainToolWindow(self) self.key_atomspec = [] self._build_ui()
def __init__(self, session, tool_name): ToolInstance.__init__(self, session, tool_name) from chimerax.ui import MainToolWindow self.tool_window = MainToolWindow(self, close_destroys=False) parent = self.tool_window.ui_area from chimerax.ui.file_history import FileHistory fh = FileHistory(session, parent, size_hint=(575, 200)) self.tool_window.manage(placement="side")
def __init__(self, session, name): super().__init__(session, name) from chimerax.ui import MainToolWindow self.tool_window = MainToolWindow(self) self.settings = _ConeAngleSettings(self.session, name) self.ligands = dict() self._build_ui()
def __init__(self, session, tool_name): super().__init__(session, tool_name) from chimerax.ui import MainToolWindow self.tool_window = MainToolWindow(self) self._build_ui() from chimerax.core.models import ADD_MODELS, REMOVE_MODELS session.triggers.add_handler(ADD_MODELS, self._fill_ui) session.triggers.add_handler(REMOVE_MODELS, self._fill_ui) self._fill_ui()
def __init__(self, session, tool_name): super().__init__(session, tool_name) self.display_name = "Toolbar" global _settings if _settings is None: _settings = _ToolbarSettings(self.session, "Toolbar") from chimerax.ui import MainToolWindow self.tool_window = MainToolWindow(self, close_destroys=False, hide_title_bar=True) self._build_ui() self.tool_window.fill_context_menu = self.fill_context_menu session.triggers.add_handler('set right mouse', self._set_right_mouse_button)
def __init__(self, session, tool_name): self._requested_halt = False self._model_move_handler = None self._last_relative_position = None self.max_steps = 2000 self.ijk_step_size_min = 0.01 self.ijk_step_size_max = 0.5 self._last_status_time = 0 self._status_interval = 0.5 # seconds ToolInstance.__init__(self, session, tool_name) from chimerax.ui import MainToolWindow tw = MainToolWindow(self) self.tool_window = tw parent = tw.ui_area from PyQt5.QtWidgets import QVBoxLayout, QLabel layout = QVBoxLayout(parent) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) parent.setLayout(layout) # Make menus to choose molecule and map for fitting mf = self._create_mol_map_menu(parent) layout.addWidget(mf) # Report correlation from chimerax.ui.widgets import EntriesRow cr = EntriesRow( parent, 'Correlation', 0.0, 'Average map value', 0.0, ('Update', lambda *args, self=self: self._update_metric_values(log=True))) self._corr_label, self._ave_label = cl, al = cr.values cl.value = al.value = None # Make fields blank # Options panel options = self._create_options_gui(parent) layout.addWidget(options) # Fit, Undo, Redo buttons bf = self._create_action_buttons(parent) layout.addWidget(bf) # Status line self._status_label = sl = QLabel(parent) layout.addWidget(sl) layout.addStretch(1) # Extra space at end tw.manage(placement="side")
def create_button_panel(self): from chimerax.ui import MainToolWindow tw = MainToolWindow(self, close_destroys=False) self.tool_window = tw p = tw.ui_area from PyQt5.QtWidgets import QVBoxLayout layout = QVBoxLayout(p) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) b = self.create_panel_buttons(p) p.setLayout(layout) layout.addWidget(b) tw.manage(placement="side")
def __init__(self, session, name): super().__init__(session, name) from chimerax.ui import MainToolWindow self.tool_window = MainToolWindow(self) self.settings = _BrowseLibSettings(self.session, name) self.showLigKeyBool = True self.showSubGhostBool = True self.showRingWalkBool = True self._build_ui()
def __init__(self, session, tool_name): ToolInstance.__init__(self, session, tool_name) from chimerax.ui import MainToolWindow self.tool_window = MainToolWindow(self) parent = self.tool_window.ui_area # UI content code from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QLabel, QHBoxLayout, QVBoxLayout, QCheckBox, QStackedWidget self.view = v = View(session.models.scene_root_model, window_size=(0, 0)) v.initialize_rendering(session.main_view.render.opengl_context) # TODO: from chimerax.graphics.camera import OrthographicCamera v.camera = OrthoCamera() if self.display_name.startswith('Top'): side = SideViewCanvas.TOP_SIDE else: side = SideViewCanvas.RIGHT_SIDE self.opengl_canvas = SideViewCanvas(parent, v, session, self, side=side) clip = QLabel(parent) clip.setText("clip:") self.clip_near = QCheckBox(parent) self.clip_near.setText("near") self.clip_near.down = False # TODO: parent.Bind(wx.EVT_CHECKBOX, self.on_near, self.clip_near) self.clip_near.clicked.connect(self.on_near) self.clip_far = QCheckBox(parent) self.clip_far.setText("far") self.clip_far.down = False # TODO: parent.Bind(wx.EVT_CHECKBOX, self.on_far, self.clip_far) self.clip_far.clicked.connect(self.on_far) button_layout = QHBoxLayout() button_layout.addWidget(clip, alignment=Qt.AlignCenter) button_layout.addWidget(self.clip_near) button_layout.addWidget(self.clip_far) button_layout.addStretch(1) class graphics_area(QStackedWidget): def sizeHint(self): # noqa from PyQt5.QtCore import QSize return QSize(200, 200) layout = QVBoxLayout() ga = graphics_area(parent) ga.addWidget(self.opengl_canvas.widget) layout.addWidget(ga, 1) layout.addLayout(button_layout) parent.setLayout(layout) self.tool_window.manage(placement="side")
class FilePanel(ToolInstance): SESSION_ENDURING = True help = "help:user/tools/filehistory.html" def __init__(self, session, tool_name): ToolInstance.__init__(self, session, tool_name) from chimerax.ui import MainToolWindow self.tool_window = MainToolWindow(self, close_destroys=False) parent = self.tool_window.ui_area from chimerax.ui.file_history import FileHistory fh = FileHistory(session, parent, size_hint=(575, 200)) self.tool_window.manage(placement="side")
def __init__(self, session, tool_name): ToolInstance.__init__(self, session, tool_name) from chimerax.ui import MainToolWindow self.tool_window = tw = MainToolWindow(self) parent = tw.ui_area from PyQt5.QtWidgets import QVBoxLayout, QDialogButtonBox layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) parent.setLayout(layout) from .gui import HBondsGUI self.gui = HBondsGUI(session, show_model_restrict=False) layout.addWidget(self.gui) from PyQt5.QtWidgets import QDialogButtonBox as qbbox bbox = qbbox(qbbox.Ok | qbbox.Apply | qbbox.Close | qbbox.Help) bbox.accepted.connect(self.run_hbonds) bbox.button(qbbox.Apply).clicked.connect(self.run_hbonds) bbox.accepted.connect( self.delete) # slots executed in the order they are connected bbox.rejected.connect(self.delete) from chimerax.core.commands import run bbox.helpRequested.connect( lambda run=run, ses=session: run(ses, "help " + self.help)) layout.addWidget(bbox) tw.manage(placement=None)
def __init__(self, gui_class, help_url, *args, **kw): super().__init__(*args, **kw) self.help = help_url from chimerax.ui import MainToolWindow self.tool_window = tw = MainToolWindow(self) parent = tw.ui_area from PyQt5.QtWidgets import QVBoxLayout, QDialogButtonBox layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) parent.setLayout(layout) self.gui = gui_class(self.session, has_apply_button=True) layout.addWidget(self.gui) from PyQt5.QtWidgets import QDialogButtonBox as qbbox bbox = qbbox(qbbox.Ok | qbbox.Apply | qbbox.Close | qbbox.Help) bbox.accepted.connect(self.run_command) bbox.button(qbbox.Apply).clicked.connect(self.run_command) bbox.accepted.connect( self.delete) # slots executed in the order they are connected bbox.rejected.connect(self.delete) if self.help: from chimerax.core.commands import run bbox.helpRequested.connect(lambda run=run, ses=self.session: run( ses, "help " + self.help)) else: bbox.button(qbbox.Help).setEnabled(False) layout.addWidget(bbox) tw.manage(placement=None)
def __init__(self, session, title): ToolInstance.__init__(self, session, title) self.title = title self._rows = None self._columns = None self._next_row_col = (1, 1) self._fill_order = 'rows' self._buttons = {} # Map (row,column) -> QPushButton from chimerax.ui import MainToolWindow tw = MainToolWindow(self) self.tool_window = tw from PyQt5.QtWidgets import QGridLayout self._layout = layout = QGridLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) tw.ui_area.setLayout(layout) tw.manage(placement="side")
def __init__(self, session, tool_name): ToolInstance.__init__(self, session, tool_name) from chimerax.ui import MainToolWindow tw = MainToolWindow(self) self.tool_window = tw parent = tw.ui_area from PyQt5.QtWidgets import QVBoxLayout, QLabel layout = QVBoxLayout(parent) layout.setContentsMargins(0,0,0,0) layout.setSpacing(0) parent.setLayout(layout) # Heading heading = QLabel('Placement of data array in x,y,z coordinate space:', parent) layout.addWidget(heading) # Make menus to choose molecule and map for fitting mf = self._create_map_menu(parent) layout.addWidget(mf) # GUI for origin, step, angles, axis settings. options = self._create_parameters_gui(parent) layout.addWidget(options) # Apply button # bf = self._create_action_buttons(parent) # layout.addWidget(bf) # Status line self._status_label = sl = QLabel(parent) layout.addWidget(sl) layout.addStretch(1) # Extra space at end self._update_gui_values() tw.manage(placement="side")
def _finalize_init(self, session, models, attr_names, scores_fetched=False): self.models = models self.attr_names = attr_names from chimerax.core.models import REMOVE_MODELS self.handlers = [ session.triggers.add_handler(REMOVE_MODELS, self._models_removed_cb) ] from chimerax.ui import MainToolWindow self.tool_window = MainToolWindow(self, close_destroys=False, statusbar=False) self.tool_window.fill_context_menu = self.fill_context_menu parent = self.tool_window.ui_area from PyQt5.QtWidgets import QTableWidget, QVBoxLayout, QAbstractItemView, QWidget, QPushButton layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) parent.setLayout(layout) self.table = QTableWidget() self.table.setSortingEnabled(True) self.table.keyPressEvent = session.ui.forward_keystroke self.table.setSelectionBehavior(QAbstractItemView.SelectRows) self.table.setSelectionMode(QAbstractItemView.ExtendedSelection) self.table.itemSelectionChanged.connect(self._table_selection_cb) self.table.setEditTriggers(QAbstractItemView.NoEditTriggers) layout.addWidget(self.table) layout.setStretchFactor(self.table, 1) self._fill_table() self.tool_window.manage('side') self.scores_fetched = scores_fetched for m in models: self.handlers.append( m.triggers.add_handler("changes", self._changes_cb))
def __init__(self, session, tool_name): ToolInstance.__init__(self, session, tool_name) self.minimize_steps = 10 self.edge_thickness = 0.1 # Edge diameter as fraction of edge length. self.display_name = 'Cage Builder' from chimerax.ui import MainToolWindow tw = MainToolWindow(self) self.tool_window = tw parent = tw.ui_area from PyQt5.QtWidgets import QHBoxLayout, QLabel, QPushButton, QLineEdit layout = QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) cp = QLabel('Create polygon') layout.addWidget(cp) b5 = QPushButton('5') b5.clicked.connect(lambda e: self.attach_polygons(5)) layout.addWidget(b5) b6 = QPushButton('6') b6.clicked.connect(lambda e: self.attach_polygons(6)) layout.addWidget(b6) mn = QPushButton('Minimize') mn.clicked.connect(self.minimize_cb) layout.addWidget(mn) ell = QLabel(' Edge length') layout.addWidget(ell) self.edge_length = el = QLineEdit('50') el.setMaximumWidth(30) layout.addWidget(el) dp = QPushButton('Delete') dp.clicked.connect(self.delete_polygon_cb) layout.addWidget(dp) layout.addStretch(1) # Extra space at end of button row. parent.setLayout(layout) tw.manage(placement="side")
def __init__(self, session, tool_name): # Standard template stuff ToolInstance.__init__(self, session, tool_name) self.display_name = "Toolshed" from chimerax.ui import MainToolWindow self.tool_window = MainToolWindow(self) self.tool_window.manage(placement=None) parent = self.tool_window.ui_area from PyQt5.QtWidgets import QGridLayout, QTabWidget from chimerax.ui.widgets import HtmlView layout = QGridLayout() layout.setContentsMargins(0, 0, 0, 0) self.tab_widget = QTabWidget() self.html_view = HtmlView(size_hint=(1000, 600), download=self._download) self.tab_widget.addTab(self.html_view, "Toolshed") layout.addWidget(self.tab_widget, 0, 0) parent.setLayout(layout) from PyQt5.QtCore import QUrl self.html_view.setUrl(QUrl(self.url)) self._pending_downloads = []
def __init__(self, session, model, filereader): super().__init__(session, model.name) from chimerax.ui import MainToolWindow self.tool_window = MainToolWindow(self) self.structure = model self.filereader = filereader self.display_name = "Thing per iteration for %s" % model.name self._last_mouse_xy = None self._dragged = False self._min_drag = 10 # pixels self._drag_mode = None self._build_ui() self.press = None self.drag_prev = None self.dragging = False self._model_closed = self.session.triggers.add_handler( REMOVE_MODELS, self.check_closed_models)
def __init__(self, session, tool_name): ToolInstance.__init__(self, session, tool_name) self.display_name = "Show Chain Sequence" self.settings = SequencesSettings(session, "ChainSequences") from chimerax.ui import MainToolWindow self.tool_window = tw = MainToolWindow(self) parent = tw.ui_area from PyQt5.QtWidgets import QVBoxLayout, QCheckBox layout = QVBoxLayout() parent.setLayout(layout) from chimerax.atomic.widgets import ChainListWidget self.chain_list = ChainListWidget(session, group_identical=self.settings.grouping, autoselect="first") self.chain_list.value_changed.connect(self._update_show_button) layout.addWidget(self.chain_list, stretch=1) self.grouping_button = QCheckBox("Group identical sequences") self.grouping_button.setChecked(self.settings.grouping) self.grouping_button.stateChanged.connect(self._grouping_change) layout.addWidget(self.grouping_button) from PyQt5.QtWidgets import QDialogButtonBox as qbbox bbox = qbbox() self._show_button = bbox.addButton("Show", qbbox.AcceptRole) bbox.addButton(qbbox.Cancel) #bbox.addButton(qbbox.Help) bbox.accepted.connect(self.show_seqs) bbox.accepted.connect(self.delete) # slots executed in the order they are connected bbox.rejected.connect(self.delete) from chimerax.core.commands import run bbox.helpRequested.connect(lambda run=run, ses=session: run(ses, "help " + self.help)) layout.addWidget(bbox) self._update_show_button() tw.manage(placement=None)
def __init__(self, session, tool_name): ToolInstance.__init__(self, session, tool_name) from chimerax.ui import MainToolWindow self.tool_window = tw = MainToolWindow(self) parent = tw.ui_area layout = QVBoxLayout() layout.setContentsMargins(0,0,0,0) layout.setSpacing(3) parent.setLayout(layout) session.logger.status("Build Structure is a work in progress, more functions coming soon...", color="blue") self.category_button = QPushButton() layout.addWidget(self.category_button, alignment=Qt.AlignCenter) cat_menu = QMenu(parent) self.category_button.setMenu(cat_menu) cat_menu.triggered.connect(self._cat_menu_cb) self.category_areas = QStackedWidget() layout.addWidget(self.category_areas) self.handlers = [] self.category_widgets = {} for category in ["Start Structure", "Modify Structure"]: self.category_widgets[category] = widget = QFrame() widget.setLineWidth(2) widget.setFrameStyle(QFrame.Panel | QFrame.Sunken) getattr(self, "_layout_" + category.lower().replace(' ', '_'))(widget) self.category_areas.addWidget(widget) cat_menu.addAction(category) initial_category = "Start Structure" self.category_button.setText(initial_category) self.category_areas.setCurrentWidget(self.category_widgets[initial_category]) tw.manage(placement="side")
def __init__(self, session, tool_name): ToolInstance.__init__(self, session, tool_name) self.display_name = "Models" self.settings = ModelPanelSettings(session, "ModelPanel") last = self.settings.last_use from time import time now = self.settings.last_use = time() short_titles = last != None and now - last < 777700 # about 3 months from chimerax.ui import MainToolWindow self.tool_window = tw = MainToolWindow(self, close_destroys=False) parent = tw.ui_area from PyQt5.QtWidgets import QTreeWidget, QHBoxLayout, QVBoxLayout, QAbstractItemView, \ QFrame, QPushButton, QSizePolicy class SizedTreeWidget(QTreeWidget): def sizeHint(self): from PyQt5.QtCore import QSize # side buttons will keep the vertical size reasonable if getattr(self, '_first_size_hint_call', True): self._first_size_hint_call = False width = 0 else: width = self.header().length() return QSize(width, 200) self.tree = SizedTreeWidget() self.tree.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.tree.keyPressEvent = session.ui.forward_keystroke self.tree.expanded.connect(self._ensure_id_width) layout = QHBoxLayout() layout.setContentsMargins(0,0,0,0) layout.setSpacing(0) layout.addWidget(self.tree) layout.setStretchFactor(self.tree, 1) parent.setLayout(layout) shown_title = "" if short_titles else "Shown" sel_title = "" if short_titles else "Select" self.tree.setHeaderLabels(["Name", "ID", " ", shown_title, sel_title]) from chimerax.ui.icons import get_qt_icon self.tree.headerItem().setIcon(3, get_qt_icon("shown")) self.tree.headerItem().setToolTip(3, "Shown") self.tree.headerItem().setIcon(4, get_qt_icon("select")) self.tree.headerItem().setToolTip(4, "Selected") self.tree.setColumnWidth(self.NAME_COLUMN, 200) self.tree.setSelectionBehavior(QAbstractItemView.SelectRows) self.tree.setSelectionMode(QAbstractItemView.ExtendedSelection) self.tree.setAnimated(True) self.tree.setUniformRowHeights(True) self.tree.setEditTriggers(QAbstractItemView.NoEditTriggers) self.tree.itemChanged.connect(self._tree_change_cb) buttons_layout = QVBoxLayout() layout.addLayout(buttons_layout) self._items = [] for model_func in [close, hide, show, view]: button = QPushButton(model_func.__name__.capitalize()) buttons_layout.addWidget(button) button.clicked.connect(lambda chk, self=self, mf=model_func, ses=session: mf([self.models[row] for row in [self._items.index(i) for i in self.tree.selectedItems()]] or self.models, ses)) self.simply_changed_models = set() self.check_model_list = True self.countdown = 1 self.self_initiated = False from chimerax.core.models import ADD_MODELS, REMOVE_MODELS, \ MODEL_DISPLAY_CHANGED, MODEL_ID_CHANGED, MODEL_NAME_CHANGED from chimerax.core.selection import SELECTION_CHANGED session.triggers.add_handler(SELECTION_CHANGED, lambda *args: self._initiate_fill_tree(*args, countdown=3)) session.triggers.add_handler(MODEL_DISPLAY_CHANGED, lambda *args: self._initiate_fill_tree(*args, simple_change=True, countdown=(0,3))) session.triggers.add_handler(ADD_MODELS, lambda *args: self._initiate_fill_tree(*args, always_rebuild=True, countdown=(3,10))) session.triggers.add_handler(REMOVE_MODELS, lambda *args: self._initiate_fill_tree(*args, always_rebuild=True)) session.triggers.add_handler(MODEL_ID_CHANGED, lambda *args: self._initiate_fill_tree(*args, always_rebuild=True, countdown=3)) session.triggers.add_handler(MODEL_NAME_CHANGED, lambda *args: self._initiate_fill_tree(*args, simple_change=True, countdown=3)) from chimerax import atomic atomic.get_triggers().add_handler("changes", self._changes_cb) self._frame_drawn_handler = None tw.manage(placement="side") tw.shown_changed = self._shown_changed
class HKLviewerTool(ToolInstance): # Inheriting from ToolInstance makes us known to the ChimeraX tool mangager, # so we can be notified and take appropriate action when sessions are closed, # saved, or restored, and we will be listed among running tools and so on. # # If cleaning up is needed on finish, override the 'delete' method # but be sure to call 'delete' from the superclass at the end. SESSION_ENDURING = False # Does this instance persist when session closes SESSION_SAVE = True # We do save/restore in sessions help = "help:user/tools/tutorial.html" # Let ChimeraX know about our help page def __init__(self, session, tool_name): # 'session' - chimerax.core.session.Session instance # 'tool_name' - string # Initialize base class. super().__init__(session, tool_name) # Set name displayed on title bar (defaults to tool_name) # Must be after the superclass init, which would override it. self.display_name = "HKLviewer" # Create the main window for our tool. The window object will have # a 'ui_area' where we place the widgets composing our interface. # The window isn't shown until we call its 'manage' method. # # Note that by default, tool windows are only hidden rather than # destroyed when the user clicks the window's close button. To change # this behavior, specify 'close_destroys=True' in the MainToolWindow # constructor. from chimerax.ui import MainToolWindow self.tool_window = MainToolWindow(self) # We will be adding an item to the tool's context menu, so override # the default MainToolWindow fill_context_menu method self.tool_window.fill_context_menu = self.fill_context_menu # Our user interface is simple enough that we could probably inline # the code right here, but for any kind of even moderately complex # interface, it is probably better to put the code in a method so # that this __init__ method remains readable. self._build_ui() self.clipper_crystdict = None session.HKLviewer = self def _build_ui(self): # Put our widgets in the tool window # We will use an editable single-line text input field (QLineEdit) # with a descriptive text label to the left of it (QLabel). To # arrange them horizontally side by side we use QHBoxLayout from Qt.QtWidgets import QHBoxLayout from . import HKLviewer hbox = QHBoxLayout() self.Guiobj = HKLviewer.run(isembedded=True, chimeraxsession=self.session) hbox.addWidget(self.Guiobj.window) self.tool_window.ui_area.setLayout(hbox) # Show the window on the user-preferred side of the ChimeraX # main window self.tool_window.manage('side') def isolde_clipper_data_to_dict(self): try: sh = self.session.isolde.selected_model.parent xmapset = sh.map_mgr.xmapsets[0] clipperlabel= list(xmapset.experimental_data.items())[0][0] labels = [lbl.strip() for lbl in clipperlabel.split(",")] self.clipper_crystdict = {} self.clipper_crystdict["spg_number"] = xmapset.spacegroup.spacegroup_number self.clipper_crystdict["unit_cell"] = (xmapset.unit_cell.cell.a, xmapset.unit_cell.cell.b, xmapset.unit_cell.cell.c, xmapset.unit_cell.cell.alpha*180/math.pi, xmapset.unit_cell.cell.beta*180/math.pi, xmapset.unit_cell.cell.gamma*180/math.pi) self.clipper_crystdict["HKL"] = xmapset.experimental_data[clipperlabel].data.data[0].tolist() self.clipper_crystdict[clipperlabel] = xmapset.experimental_data[clipperlabel].data.data[1].transpose().tolist() self.clipper_crystdict["FCALC,PHFCALC"] = xmapset.live_xmap_mgr.f_calc.data[1].transpose().tolist() self.clipper_crystdict["2FOFC,PH2FOFC"] = xmapset.live_xmap_mgr.base_2fofc.data[1].transpose().tolist() except Exception as e: pass def return_pressed(self): # The use has pressed the Return key; log the current text as HTML from chimerax.core.commands import run # ToolInstance has a 'session' attribute... run(self.session, "log html %s" % self.line_edit.text()) def fill_context_menu(self, menu, x, y): # Add any tool-specific items to the given context menu (a QMenu instance). # The menu will then be automatically filled out with generic tool-related actions # (e.g. Hide Tool, Help, Dockable Tool, etc.) # # The x,y args are the x() and y() values of QContextMenuEvent, in the rare case # where the items put in the menu depends on where in the tool interface the menu # was raised. from PyQt5.QtWidgets import QAction settings_action = QAction("HKLviewer settings", menu) settings_action.triggered.connect(lambda *args: self.Guiobj.SettingsDialog() ) menu.addAction(settings_action) def delete(self): from Qt.QtCore import QEvent self.session.triggers.remove_handler(self.Guiobj.chimeraxprocmsghandler) self.Guiobj.closeEvent(QEvent.Close) super(HKLviewerTool, self).delete() def take_snapshot(self, session, flags): return { 'version': 1, 'current text': self.line_edit.text() } @classmethod def restore_snapshot(class_obj, session, data): # Instead of using a fixed string when calling the constructor below, we could # have saved the tool name during take_snapshot() (from self.tool_name, inherited # from ToolInstance) and used that saved tool name. There are pros and cons to # both approaches. inst = class_obj(session, "cctbx.HKLviewer") inst.line_edit.setText(data['current text']) return inst
class ConeAngle(ToolInstance): help = "https://github.com/QChASM/SEQCROW/wiki/Cone-Angle-Tool" def __init__(self, session, name): super().__init__(session, name) from chimerax.ui import MainToolWindow self.tool_window = MainToolWindow(self) self.settings = _ConeAngleSettings(self.session, name) self.ligands = dict() self._build_ui() def _build_ui(self): layout = QFormLayout() self.cone_option = QComboBox() self.cone_option.addItems(["Tolman (Unsymmetrical)", "Exact"]) ndx = self.cone_option.findText(self.settings.cone_option, Qt.MatchExactly) self.cone_option.setCurrentIndex(ndx) layout.addRow("method:", self.cone_option) self.radii_option = QComboBox() self.radii_option.addItems(["Bondi", "UMN"]) ndx = self.radii_option.findText(self.settings.radii, Qt.MatchExactly) self.radii_option.setCurrentIndex(ndx) layout.addRow("radii:", self.radii_option) self.display_cone = QCheckBox() self.display_cone.setChecked(self.settings.display_cone) layout.addRow("show cone:", self.display_cone) self.display_radii = QCheckBox() self.display_radii.setChecked(self.settings.display_radii) layout.addRow("show radii:", self.display_radii) set_ligand_button = QPushButton("set ligand to current selection") set_ligand_button.clicked.connect(self.set_ligand) layout.addRow(set_ligand_button) self.set_ligand_button = set_ligand_button calc_cone_button = QPushButton( "calculate cone angle for ligand on selected center") calc_cone_button.clicked.connect(self.calc_cone) layout.addRow(calc_cone_button) self.calc_cone_button = calc_cone_button remove_cone_button = QPushButton("remove cone visualizations") remove_cone_button.clicked.connect(self.del_cone) layout.addRow(remove_cone_button) self.remove_cone_button = remove_cone_button self.table = QTableWidget() self.table.setColumnCount(3) self.table.setHorizontalHeaderLabels([ 'model', 'center', 'cone angle (°)', ]) self.table.setSelectionBehavior(QTableWidget.SelectRows) self.table.setEditTriggers(QTableWidget.NoEditTriggers) self.table.resizeColumnToContents(0) self.table.resizeColumnToContents(1) self.table.resizeColumnToContents(2) self.table.horizontalHeader().setSectionResizeMode( 2, QHeaderView.Stretch) layout.addRow(self.table) menu = QMenuBar() export = menu.addMenu("&Export") clear = QAction("Clear data table", self.tool_window.ui_area) clear.triggered.connect(self.clear_table) export.addAction(clear) copy = QAction("&Copy CSV to clipboard", self.tool_window.ui_area) copy.triggered.connect(self.copy_csv) shortcut = QKeySequence(Qt.CTRL + Qt.Key_C) copy.setShortcut(shortcut) export.addAction(copy) save = QAction("&Save CSV...", self.tool_window.ui_area) save.triggered.connect(self.save_csv) #this shortcut interferes with main window's save shortcut #I've tried different shortcut contexts to no avail #thanks Qt... #shortcut = QKeySequence(Qt.CTRL + Qt.Key_S) #save.setShortcut(shortcut) #save.setShortcutContext(Qt.WidgetShortcut) export.addAction(save) delimiter = export.addMenu("Delimiter") comma = QAction("comma", self.tool_window.ui_area, checkable=True) comma.setChecked(self.settings.delimiter == "comma") comma.triggered.connect(lambda *args, delim="comma": self.settings. __setattr__("delimiter", delim)) delimiter.addAction(comma) tab = QAction("tab", self.tool_window.ui_area, checkable=True) tab.setChecked(self.settings.delimiter == "tab") tab.triggered.connect(lambda *args, delim="tab": self.settings. __setattr__("delimiter", delim)) delimiter.addAction(tab) space = QAction("space", self.tool_window.ui_area, checkable=True) space.setChecked(self.settings.delimiter == "space") space.triggered.connect(lambda *args, delim="space": self.settings. __setattr__("delimiter", delim)) delimiter.addAction(space) semicolon = QAction("semicolon", self.tool_window.ui_area, checkable=True) semicolon.setChecked(self.settings.delimiter == "semicolon") semicolon.triggered.connect(lambda *args, delim="semicolon": self. settings.__setattr__("delimiter", delim)) delimiter.addAction(semicolon) add_header = QAction("&Include CSV header", self.tool_window.ui_area, checkable=True) add_header.setChecked(self.settings.include_header) add_header.triggered.connect(self.header_check) export.addAction(add_header) comma.triggered.connect( lambda *args, action=tab: action.setChecked(False)) comma.triggered.connect( lambda *args, action=space: action.setChecked(False)) comma.triggered.connect( lambda *args, action=semicolon: action.setChecked(False)) tab.triggered.connect( lambda *args, action=comma: action.setChecked(False)) tab.triggered.connect( lambda *args, action=space: action.setChecked(False)) tab.triggered.connect( lambda *args, action=semicolon: action.setChecked(False)) space.triggered.connect( lambda *args, action=comma: action.setChecked(False)) space.triggered.connect( lambda *args, action=tab: action.setChecked(False)) space.triggered.connect( lambda *args, action=semicolon: action.setChecked(False)) semicolon.triggered.connect( lambda *args, action=comma: action.setChecked(False)) semicolon.triggered.connect( lambda *args, action=tab: action.setChecked(False)) semicolon.triggered.connect( lambda *args, action=space: action.setChecked(False)) menu.setNativeMenuBar(False) self._menu = menu layout.setMenuBar(menu) menu.setVisible(True) self.tool_window.ui_area.setLayout(layout) self.tool_window.manage(None) def clear_table(self): are_you_sure = QMessageBox.question( None, "Clear table?", "Are you sure you want to clear the data table?", ) if are_you_sure != QMessageBox.Yes: return self.table.setRowCount(0) def header_check(self, state): """user has [un]checked the 'include header' option on the menu""" if state: self.settings.include_header = True else: self.settings.include_header = False def get_csv(self): if self.settings.delimiter == "comma": delim = "," elif self.settings.delimiter == "space": delim = " " elif self.settings.delimiter == "tab": delim = "\t" elif self.settings.delimiter == "semicolon": delim = ";" if self.settings.include_header: s = delim.join(["model", "center_atom", "cone_angle"]) s += "\n" else: s = "" for i in range(0, self.table.rowCount()): s += delim.join([ item.data(Qt.DisplayRole) for item in [self.table.item(i, j) for j in range(0, 3)] ]) s += "\n" return s def copy_csv(self): app = QApplication.instance() clipboard = app.clipboard() csv = self.get_csv() clipboard.setText(csv) self.session.logger.status("copied to clipboard") def save_csv(self): """save data on current tab to CSV file""" filename, _ = QFileDialog.getSaveFileName(filter="CSV Files (*.csv)") if filename: s = self.get_csv() with open(filename, 'w') as f: f.write(s.strip()) self.session.logger.status("saved to %s" % filename) def del_cone(self): for model in self.session.models.list(type=Generic3DModel): if model.name.startswith("Cone angle"): model.delete() def set_ligand(self, *args): self.ligands = {} for atom in selected_atoms(self.session): if atom.structure not in self.ligands: self.ligands[atom.structure] = [] self.ligands[atom.structure].append(atom) self.session.logger.status("set ligand to current selection") def calc_cone(self, *args): self.settings.cone_option = self.cone_option.currentText() self.settings.radii = self.radii_option.currentText() self.settings.display_radii = self.display_radii.checkState( ) == Qt.Checked self.settings.display_cone = self.display_cone.checkState( ) == Qt.Checked if self.cone_option.currentText() == "Tolman (Unsymmetrical)": method = "tolman" else: method = self.cone_option.currentText() radii = self.radii_option.currentText() return_cones = self.display_cone.checkState() == Qt.Checked display_radii = self.display_radii.checkState() == Qt.Checked # self.table.setRowCount(0) for center_atom in selected_atoms(self.session): rescol = ResidueCollection(center_atom.structure) at_center = rescol.find_exact(AtomSpec(center_atom.atomspec))[0] if center_atom.structure in self.ligands: comp = Component( rescol.find([ AtomSpec(atom.atomspec) for atom in self.ligands[center_atom.structure] ]), to_center=rescol.find_exact(AtomSpec( center_atom.atomspec)), key_atoms=rescol.find(BondedTo(at_center)), ) else: comp = Component( rescol.find(NotAny(at_center)), to_center=rescol.find_exact(AtomSpec( center_atom.atomspec)), key_atoms=rescol.find(BondedTo(at_center)), ) cone_angle = comp.cone_angle( center=rescol.find(AtomSpec(center_atom.atomspec)), method=method, radii=radii, return_cones=return_cones, ) if return_cones: cone_angle, cones = cone_angle s = ".transparency 0.5\n" for cone in cones: apex, base, radius = cone s += ".cone %6.3f %6.3f %6.3f %6.3f %6.3f %6.3f %.3f open\n" % ( *apex, *base, radius) stream = BytesIO(bytes(s, "utf-8")) bild_obj, status = read_bild(self.session, stream, "Cone angle %s" % center_atom) self.session.models.add(bild_obj, parent=center_atom.structure) if display_radii: s = ".note radii\n" s += ".transparency 75\n" color = None for atom in comp.atoms: chix_atom = atom.chix_atom if radii.lower() == "umn": r = VDW_RADII[chix_atom.element.name] elif radii.lower() == "bondi": r = BONDI_RADII[chix_atom.element.name] if color is None or chix_atom.color != color: color = chix_atom.color rgb = [x / 255. for x in chix_atom.color] rgb.pop(-1) s += ".color %f %f %f\n" % tuple(rgb) s += ".sphere %f %f %f %f\n" % (*chix_atom.coord, r) stream = BytesIO(bytes(s, "utf-8")) bild_obj, status = read_bild(self.session, stream, "Cone angle radii") self.session.models.add(bild_obj, parent=center_atom.structure) row = self.table.rowCount() self.table.insertRow(row) name = QTableWidgetItem() name.setData(Qt.DisplayRole, center_atom.structure.name) self.table.setItem(row, 0, name) center = QTableWidgetItem() center.setData(Qt.DisplayRole, center_atom.atomspec) self.table.setItem(row, 1, center) ca = QTableWidgetItem() ca.setData(Qt.DisplayRole, "%.2f" % cone_angle) self.table.setItem(row, 2, ca) self.table.resizeColumnToContents(0) self.table.resizeColumnToContents(1) self.table.resizeColumnToContents(2)
class AaronTools_Library(ToolInstance): help = "https://github.com/QChASM/SEQCROW/wiki/Browse-AaronTools-Libraries-Tool" SESSION_ENDURING = True SESSION_SAVE = True def __init__(self, session, name): super().__init__(session, name) from chimerax.ui import MainToolWindow self.tool_window = MainToolWindow(self) self.settings = _BrowseLibSettings(self.session, name) self.showLigKeyBool = True self.showSubGhostBool = True self.showRingWalkBool = True self._build_ui() def _build_ui(self): layout = QGridLayout() self.library_tabs = QTabWidget() #add a tab for ligands self.ligand_tab = QWidget() self.ligand_layout = QVBoxLayout(self.ligand_tab) self.lig_table = LigandTable() self.ligand_layout.addWidget(self.lig_table) showKeyAtomsCheck = QCheckBox('show key atoms') showKeyAtomsCheck.setToolTip( "ligand's coordinating atoms will be highlighted") showKeyAtomsCheck.toggle() showKeyAtomsCheck.stateChanged.connect(self.showKeyAtoms) self.ligand_layout.addWidget(showKeyAtomsCheck) self.lig_color = ColorButton('key atom color', has_alpha_channel=True) self.lig_color.setToolTip("highlight color for ligand's key atoms") self.lig_color.set_color(self.settings.key_atom_color) self.ligand_layout.addWidget(self.lig_color) openLigButton = QPushButton("open selected ligands") openLigButton.setToolTip( "ligands selected in the table will be loaded into ChimeraX") openLigButton.clicked.connect(self.open_ligands) self.ligand_layout.addWidget(openLigButton) self.openLigButton = openLigButton #add a tab for substituents self.substituent_tab = QWidget() self.substituent_layout = QVBoxLayout(self.substituent_tab) self.sub_table = SubstituentTable() self.substituent_layout.addWidget(self.sub_table) showGhostConnectionCheck = QCheckBox('show ghost connection') showGhostConnectionCheck.setToolTip( "ligand's coordinating atoms will be highlighted") showGhostConnectionCheck.toggle() showGhostConnectionCheck.stateChanged.connect(self.showGhostConnection) self.substituent_layout.addWidget(showGhostConnectionCheck) self.sub_color = ColorButton('ghost connection color', has_alpha_channel=True) self.sub_color.setToolTip("color of ghost connection") self.sub_color.set_color(self.settings.ghost_connection_color) self.substituent_layout.addWidget(self.sub_color) openSubButton = QPushButton("open selected substituents") openSubButton.setToolTip( "substituents selected in the table will be loaded into ChimeraX") openSubButton.clicked.connect(self.open_substituents) self.substituent_layout.addWidget(openSubButton) self.openSubButton = openSubButton #add a tab for rings self.ring_tab = QWidget() self.ring_layout = QVBoxLayout(self.ring_tab) self.ring_table = RingTable() self.ring_layout.addWidget(self.ring_table) showRingWalkCheck = QCheckBox('show ring walk') showRingWalkCheck.setToolTip( "arrows will show the way AaronTools traverses the ring") showRingWalkCheck.toggle() showRingWalkCheck.stateChanged.connect(self.showRingWalk) self.ring_layout.addWidget(showRingWalkCheck) self.ring_color = ColorButton('walk arrow color', has_alpha_channel=True) self.ring_color.setToolTip("color of walk arrows") self.ring_color.set_color(self.settings.ring_walk_color) self.ring_layout.addWidget(self.ring_color) openRingButton = QPushButton("open selected rings") openRingButton.setToolTip( "rings selected in the table will be loaded into ChimeraX") openRingButton.clicked.connect(self.open_rings) self.ring_layout.addWidget(openRingButton) self.openRingButton = openRingButton self.library_tabs.resize(300, 200) self.library_tabs.addTab(self.ligand_tab, "ligands") self.library_tabs.addTab(self.substituent_tab, "substituents") self.library_tabs.addTab(self.ring_tab, "rings") layout.addWidget(self.library_tabs) self.tool_window.ui_area.setLayout(layout) self.tool_window.manage(None) def showKeyAtoms(self, state): if state == QtCore.Qt.Checked: self.showLigKeyBool = True else: self.showLigKeyBool = False def open_ligands(self): for row in self.lig_table.table.selectionModel().selectedRows(): if self.lig_table.table.isRowHidden(row.row()): continue lig_name = row.data() ligand = Component(lig_name) chimera_ligand = ResidueCollection( ligand, name=lig_name).get_chimera(self.session) self.session.models.add([chimera_ligand]) apply_seqcrow_preset(chimera_ligand, fallback="Ball-Stick-Endcap") if self.showLigKeyBool: color = self.lig_color.get_color() color = [c / 255. for c in color] self.settings.key_atom_color = tuple(color) bild_obj = key_atom_highlight(ligand, color, self.session) self.session.models.add(bild_obj, parent=chimera_ligand) def showGhostConnection(self, state): if state == QtCore.Qt.Checked: self.showSubGhostBool = True else: self.showSubGhostBool = False def open_substituents(self): for row in self.sub_table.table.selectionModel().selectedRows(): if self.sub_table.table.isRowHidden(row.row()): continue sub_name = row.data() substituent = Substituent(sub_name, name=sub_name) chimera_substituent = ResidueCollection(substituent).get_chimera( self.session) self.session.models.add([chimera_substituent]) apply_seqcrow_preset(chimera_substituent, fallback="Ball-Stick-Endcap") if self.showSubGhostBool: color = self.sub_color.get_color() color = [c / 255. for c in color] self.settings.ghost_connection_color = tuple(color) bild_obj = ghost_connection_highlight(substituent, color, self.session) self.session.models.add(bild_obj, parent=chimera_substituent) def showRingWalk(self, state): if state == QtCore.Qt.Checked: self.showRingWalkBool = True else: self.showRingWalkBool = False def open_rings(self): for row in self.ring_table.table.selectionModel().selectedRows(): if self.ring_table.table.isRowHidden(row.row()): continue ring_name = row.data() ring = Ring(ring_name, name=ring_name) chimera_ring = ResidueCollection(ring.copy()).get_chimera( self.session) self.session.models.add([chimera_ring]) apply_seqcrow_preset(chimera_ring, fallback="Ball-Stick-Endcap") if self.showRingWalkBool: color = self.ring_color.get_color() color = [c / 255. for c in color] self.ring_walk_color = tuple(color) bild_obj = show_walk_highlight(ring, chimera_ring, color, self.session) self.session.models.add(bild_obj, parent=chimera_ring) def display_help(self): """Show the help for this tool in the help viewer.""" from chimerax.core.commands import run run(self.session, 'open %s' % self.help if self.help is not None else "")
def __init__(self, session): tool_name = "Help Viewer" ToolInstance.__init__(self, session, tool_name) self._pending_downloads = [] from chimerax.ui import MainToolWindow self.tool_window = MainToolWindow(self) parent = self.tool_window.ui_area # UI content code from PyQt5.QtWidgets import QToolBar, QVBoxLayout, QAction, QLineEdit, QTabWidget, QShortcut, QStatusBar from PyQt5.QtGui import QIcon from PyQt5.QtCore import Qt shortcuts = ( (Qt.CTRL + Qt.Key_0, self.page_reset_zoom), (Qt.CTRL + Qt.Key_T, lambda: self.create_tab(empty=True)), (Qt.CTRL + Qt.Key_W, self.close_current_tab), (Qt.CTRL + Qt.Key_Tab, lambda: self.cycle_tab(1)), (Qt.CTRL + Qt.SHIFT + Qt.Key_Tab, lambda: self.cycle_tab(-1)), (Qt.CTRL + Qt.Key_1, lambda: self.tab_n(0)), (Qt.CTRL + Qt.Key_2, lambda: self.tab_n(1)), (Qt.CTRL + Qt.Key_3, lambda: self.tab_n(2)), (Qt.CTRL + Qt.Key_4, lambda: self.tab_n(3)), (Qt.CTRL + Qt.Key_5, lambda: self.tab_n(4)), (Qt.CTRL + Qt.Key_6, lambda: self.tab_n(5)), (Qt.CTRL + Qt.Key_7, lambda: self.tab_n(6)), (Qt.CTRL + Qt.Key_8, lambda: self.tab_n(7)), (Qt.CTRL + Qt.Key_9, lambda: self.tab_n(-1)), ) for shortcut, callback in shortcuts: sc = QShortcut(shortcut, parent) sc.activated.connect(callback) self.toolbar = tb = QToolBar() # tb.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) layout = QVBoxLayout() layout.setContentsMargins(0, 1, 0, 0) layout.addWidget(tb) parent.setLayout(layout) import os.path icon_dir = os.path.dirname(__file__) # attribute, text, tool tip, callback, shortcut(s), enabled buttons = ( ("back", "Back", "Back to previous page", self.page_back, Qt.Key_Back, False), ("forward", "Forward", "Next page", self.page_forward, Qt.Key_Forward, False), ("reload", "Reload", "Reload page", self.page_reload, Qt.Key_Reload, True), ("new_tab", "New Tab", "New Tab", lambda: self.create_tab(empty=True), Qt.Key_Reload, True), ("zoom_in", "Zoom in", "Zoom in", self.page_zoom_in, [Qt.CTRL + Qt.Key_Plus, Qt.Key_ZoomIn, Qt.CTRL + Qt.Key_Equal], True), ("zoom_out", "Zoom out", "Zoom out", self.page_zoom_out, [Qt.CTRL + Qt.Key_Minus, Qt.Key_ZoomOut], True), ("home", "Home", "Home page", self.page_home, Qt.Key_HomePage, True), (None, None, None, None, None, None), ("search", "Search", "Search in page", self.page_search, Qt.Key_Search, True), ) for attribute, text, tooltip, callback, shortcut, enabled in buttons: if attribute is None: tb.addSeparator() continue icon_path = os.path.join(icon_dir, "%s.svg" % attribute) setattr(self, attribute, QAction(QIcon(icon_path), text, tb)) a = getattr(self, attribute) a.setToolTip(tooltip) a.triggered.connect(callback) if shortcut: if isinstance(shortcut, list): a.setShortcuts(shortcut) else: a.setShortcut(shortcut) a.setEnabled(enabled) tb.addAction(a) self.url = QLineEdit() self.url.setPlaceholderText("url") self.url.setClearButtonEnabled(True) self.url.returnPressed.connect(self.go_to) tb.insertWidget(self.reload, self.url) self.search_terms = QLineEdit() self.search_terms.setClearButtonEnabled(True) self.search_terms.setPlaceholderText("search in page") self.search_terms.setMaximumWidth(200) self.search_terms.returnPressed.connect(self.page_search) tb.addWidget(self.search_terms) self.tabs = QTabWidget(parent) self.tabs.setTabsClosable(True) self.tabs.setUsesScrollButtons(True) self.tabs.setTabBarAutoHide(True) self.tabs.setDocumentMode(False) self.tabs.currentChanged.connect(self.tab_changed) self.tabs.tabCloseRequested.connect(self.close_tab) layout.addWidget(self.tabs) self.status_bar = QStatusBar() layout.addWidget(self.status_bar) self.tool_window.manage(placement=None) self.profile = create_chimerax_profile( self.tabs, interceptor=self.intercept, download=self.download_requested)