Exemplo n.º 1
0
    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
Exemplo n.º 2
0
    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")
Exemplo n.º 3
0
 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")
Exemplo n.º 4
0
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")
Exemplo n.º 5
0
    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")
Exemplo n.º 6
0
    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")
Exemplo n.º 7
0
  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")
Exemplo n.º 8
0
class ShellUI(ToolInstance):

    # shell tool does not participate in sessions
    SESSION_ENDURING = True

    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)
Exemplo n.º 9
0
class SideViewUI(ToolInstance):

    help = "help:user/tools/sideview.html"

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

    def delete(self):
        self.opengl_canvas.close()
        self.view.delete()
        self.view = None
        ToolInstance.delete(self)

    def on_near(self, event):
        session = self._session()
        v = session.main_view
        planes = v.clip_planes
        if not self.clip_near.isChecked():
            planes.remove_plane('near')
            return
        p = planes.find_plane('near')
        if p:
            return
        b = v.drawing_bounds()
        if b is None:
            session.logger.info("Can not turn on clipping since there are no models to clip")
            self.clip_near.setChecked(False)
            return
        near, far = v.near_far_distances(v.camera, None)
        camera_pos = v.camera.position.origin()
        vd = v.camera.view_direction()
        plane_point = camera_pos + near * vd
        planes.set_clip_position('near', plane_point, v)

    def on_far(self, event):
        session = self._session()
        v = session.main_view
        planes = v.clip_planes
        if not self.clip_far.isChecked():
            planes.remove_plane('far')
            return
        p = planes.find_plane('far')
        if p:
            return
        b = v.drawing_bounds()
        if b is None:
            session.logger.info("Can not turn on clipping since there are no models to clip")
            self.clip_far.setChecked(False)
            return
        near, far = v.near_far_distances(v.camera, None)
        camera_pos = v.camera.position.origin()
        vd = v.camera.view_direction()
        plane_point = camera_pos + far * vd
        planes.set_clip_position('far', plane_point, v)
Exemplo n.º 10
0
class ToolshedUI(ToolInstance):

    SESSION_ENDURING = True

    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 = []

    @property
    def url(self):
        from urllib.parse import urlencode
        from distutils.util import get_platform
        from chimerax.core import buildinfo
        params = urlencode({"platform":get_platform(),
                            "version":buildinfo.version})
        url = "%s?%s" % (self.session.toolshed.remote_url, params)
        return url

    def _intercept(self, info):
        # "info" is an instance of QWebEngineUrlRequestInfo
        qurl = info.requestUrl()
        # print("intercept", qurl.toString())

    def _download(self, item):
        # "item" is an instance of QWebEngineDownloadItem
        # print("ToolshedUI._download", item)
        import os.path, os
        urlFile = item.url().fileName()
        base, extension = os.path.splitext(urlFile)
        item.finished.connect(self._download_finished)
        # print("ToolshedUI._download connect", item.mimeType(), extension)
        # Normally, we would look at the download type or MIME type,
        # but since neither one is set by the server, we look at the
        # download extension instead
        if extension == ".whl":
            # Since the file name encodes the package name and version
            # number, we make sure that we are using the right name
            # instead of whatever QWebEngine may want to use.
            # Remove _# which may be present if bundle author submitted
            # the same version of the bundle multiple times.
            parts = base.rsplit('_', 1)
            if len(parts) == 2 and parts[1].isdigit():
                urlFile = parts[0] + extension
            filePath = os.path.join(os.path.dirname(item.path()), urlFile)
            item.setPath(filePath)
            # print("ToolshedUI._download clean")
            try:
                # Guarantee that file name is available
                os.remove(filePath)
            except OSError:
                pass
            self._pending_downloads.append(item)
            self.session.logger.info("Downloading bundle %s" % urlFile)
            # print("ToolshedUI._download accept")
            item.accept()
        else:
            # print("ToolshedUI._download cancel")
            item.cancel()

    def _download_finished(self, *args, **kw):
        # print("ToolshedUI._download_finished", args, kw)
        finished = []
        pending = []
        for item in self._pending_downloads:
            if not item.isFinished():
                pending.append(item)
            else:
                finished.append(item)
        self._pending_downloads = pending
        install_cmd = ["install", "--quiet"]
        need_reload = False
        for item in finished:
            item.finished.disconnect()
            filename = item.path()
            from chimerax.ui.ask import ask
            how = ask(self.session,
                      "Install %s for:" % filename,
                      ["all users", "just me", "cancel"],
                      title="Toolshed")
            if how == "cancel":
                self.session.logger.info("Bundle installation canceled")
                continue
            elif how == "just me":
                per_user = True
            else:
                per_user = False
            self.session.toolshed.install_bundle(filename,
                                                 self.session.logger,
                                                 per_user=per_user,
                                                 session=self.session)

    def _navigate(self, qurl):
        session = self.session
        # Handle event
        # data is QUrl
        url = qurl.toString()

        def link_handled():
            return False
        if url.startswith("toolshed:"):
            link_handled()
            parts = url.split(':')
            method = getattr(self, parts[1])
            args = parts[2:]
            method(session, *args)

    def _make_page(self, *args):
        session = self.session
        ts = session.toolshed
        tools = session.tools
        from io import StringIO
        page = _PageTemplate

        # TODO: handle multiple versions of available tools
        # TODO: add "update" link for installed tools

        # running
        def tool_key(t):
            return t.display_name
        s = StringIO()
        tool_list = tools.list()
        if not tool_list:
            print('<p class="empty">No running tools found.</p>', file=s)
        else:
            print("<table>", file=s)
            for t in sorted(tool_list, key=tool_key):
                show_link = _SHOW_LINK % t.id
                hide_link = _HIDE_LINK % t.id
                kill_link = _KILL_LINK % t.id
                links = "&nbsp;".join([show_link, hide_link, kill_link])
                print(_RUNNING_ROW % (links, t.display_name), file=s)
            print("</table>", file=s)
        page = page.replace("RUNNING_TOOLS", s.getvalue())

        # installed
        def bundle_key(bi):
            return bi.name
        s = StringIO()
        bi_list = ts.bundle_info(session.logger, installed=True, available=False)
        if not bi_list:
            print('<p class="empty">No installed tools found.</p>', file=s)
        else:
            print("<table>", file=s)
            for bi in sorted(bi_list, key=bundle_key):
                start_link = _START_LINK % bi.name
                update_link = _UPDATE_LINK % bi.name
                remove_link = _REMOVE_LINK % bi.name
                links = "&nbsp;".join([start_link, update_link, remove_link])
                print(_ROW % (links, bi.name, bi.synopsis), file=s)
            print("</table>", file=s)
        page = page.replace("INSTALLED_TOOLS", s.getvalue())
        installed_bundles = dict([(bi.name, bi) for bi in bi_list])

        # available
        s = StringIO()
        bi_list = ts.bundle_info(installed=False, available=True)
        if not bi_list:
            print('<p class="empty">No available tools found.</p>', file=s)
        else:
            any_shown = False
            for bi in sorted(bi_list, key=bundle_key):
                try:
                    # If this bundle is already installed, do not display it
                    installed = installed_bundles[bi.name]
                    if installed.version == bi.version:
                        continue
                except KeyError:
                    pass
                if not any_shown:
                    print("<table>", file=s)
                    any_shown = True
                link = _INSTALL_LINK % bi.name
                print(_ROW % (link, bi.name, bi.synopsis), file=s)
            if any_shown:
                print("</table>", file=s)
            else:
                print('<p class="empty">All available tools are installed.</p>', file=s)
        page = page.replace("AVAILABLE_TOOLS", s.getvalue())
        page = page.replace("TOOLSHED_URL", ts.remote_url)

        self.webview.history().clear()
        self.webview.setHtml(page)

    def refresh_installed(self, session):
        # refresh list of installed tools
        from . import cmd
        cmd.ts_refresh(session, bundle_type="installed")
        self._make_page()

    def refresh_available(self, session):
        # refresh list of available tools
        from . import cmd
        cmd.ts_refresh(session, bundle_type="available")
        self._make_page()

    def _start_tool(self, session, tool_name):
        # start installed tool
        from . import cmd
        cmd.ts_start(session, tool_name)
        self._make_page()

    def _update_tool(self, session, tool_name):
        # update installed tool
        from . import cmd
        cmd.ts_update(session, tool_name)
        self._make_page()

    def _remove_tool(self, session, tool_name):
        # remove installed tool
        from . import cmd
        cmd.ts_remove(session, tool_name)
        self._make_page()

    def _install_tool(self, session, tool_name):
        # install available tool
        from . import cmd
        cmd.ts_install(session, tool_name)
        self._make_page()

    def _find_tool(self, session, tool_id):
        t = session.tools.find_by_id(int(tool_id))
        if t is None:
            raise RuntimeError("cannot find tool with id \"%s\"" % tool_id)
        return t

    def _show_tool(self, session, tool_id):
        self._find_tool(session, tool_id).display(True)
        self._make_page()

    def _hide_tool(self, session, tool_id):
        self._find_tool(session, tool_id).display(False)
        self._make_page()

    def _kill_tool(self, session, tool_id):
        self._find_tool(session, tool_id).delete()
        self._make_page()

    def button_test(self, session, *args):
        session.logger.info("ToolshedUI.button_test: %s" % str(args))

    @classmethod
    def get_singleton(cls, session):
        from chimerax.core import tools
        return tools.get_singleton(session, ToolshedUI, 'Toolshed')

    #
    # Override ToolInstance methods
    #
    def delete(self):
        self.tool_window = None
        super().delete()
Exemplo n.º 11
0
class ToolbarTool(ToolInstance):

    SESSION_ENDURING = True
    SESSION_SAVE = False
    PLACEMENT = "top"
    CUSTOM_SCHEME = "toolbar"
    help = "help:user/tools/toolbar.html"  # Let ChimeraX know about our help page

    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 _build_ui(self):
        from chimerax.ui.widgets.tabbedtoolbar import TabbedToolbar
        from PyQt5.QtWidgets import QVBoxLayout
        layout = QVBoxLayout()
        margins = layout.contentsMargins()
        margins.setTop(0)
        margins.setBottom(0)
        layout.setContentsMargins(margins)
        self.ttb = TabbedToolbar(
            self.tool_window.ui_area, show_section_titles=_settings.show_section_labels,
            show_button_titles=_settings.show_button_labels)
        layout.addWidget(self.ttb)
        self._build_tabs()
        self.tool_window.ui_area.setLayout(layout)
        self.tool_window.manage(self.PLACEMENT)

    def fill_context_menu(self, menu, x, y):
        # avoid having actions destroyed when this routine returns
        # by stowing a reference in the menu itself
        from PyQt5.QtWidgets import QAction
        button_labels = QAction("Show button labels", menu)
        button_labels.setCheckable(True)
        button_labels.setChecked(_settings.show_button_labels)
        button_labels.toggled.connect(lambda arg, f=self._set_button_labels: f(arg))
        menu.addAction(button_labels)
        section_labels = QAction("Show section labels", menu)
        section_labels.setCheckable(True)
        section_labels.setChecked(_settings.show_section_labels)
        section_labels.toggled.connect(lambda arg, f=self._set_section_labels: f(arg))
        menu.addAction(section_labels)
        settings_action = QAction("Settings...", menu)
        settings_action.triggered.connect(lambda arg: self.show_settings())
        menu.addAction(settings_action)

    def show_settings(self):
        if not hasattr(self, "settings_tool"):
            self.settings_tool = ToolbarSettingsTool(
                self.session, self,
                self.tool_window.create_child_window("Toolbar Settings", close_destroys=False))
            self.settings_tool.tool_window.manage(None)
        self.settings_tool.tool_window.shown = True

    def _set_button_labels(self, show_button_labels):
        _settings.show_button_labels = show_button_labels
        self.ttb.set_show_button_titles(show_button_labels)

    def _set_section_labels(self, show_section_labels):
        _settings.show_section_labels = show_section_labels
        self.ttb.set_show_section_titles(show_section_labels)

    def build_home_tab(self):
        # (re)Build Home tab from settings
        from PyQt5.QtGui import QIcon
        self.ttb.clear_tab("Home")
        last_section = None
        for (section, compact, display_name, icon_path, description, link, bundle_info, name, kw) in _home_layout(self.session, _settings.home_tab):
            if section != last_section:
                last_section = section
                if compact:
                    self.ttb.set_section_compact("Home", section, True)
            if icon_path is None:
                icon = None
            else:
                icon = QIcon(icon_path)

            def callback(event, session=self.session, name=name, bundle_info=bundle_info, display_name=display_name):
                bundle_info.run_provider(session, name, session.toolbar, display_name=display_name)
            self.ttb.add_button(
                    "Home", section, display_name, callback,
                    icon, description, **kw)

    def _build_tabs(self):
        # add buttons from toolbar manager
        from PyQt5.QtGui import QIcon
        from .manager import fake_mouse_mode_bundle_info
        self.right_mouse_buttons = {}
        self.current_right_mouse_button = None

        self.build_home_tab()

        # Build other tabs from toolbar manager
        toolbar = self.session.toolbar._toolbar
        last_tab = None
        last_section = None
        for (tab, section, compact, display_name, icon_path, description, bundle_info, name, kw) in _other_layout(self.session, toolbar):
            if tab != last_tab:
                last_tab = tab
                last_section = None
            if section != last_section:
                last_section = section
                if compact:
                    self.ttb.set_section_compact(tab, section, True)
            if bundle_info == fake_mouse_mode_bundle_info:
                kw["vr_mode"] = name  # Allows VR to recognize mouse mode buttons
                rmbs = self.right_mouse_buttons.setdefault(name, [])
                if icon_path is None:
                    m = self.session.ui.mouse_modes.named_mode(name)
                    if m is not None:
                        icon_path = m.icon_path
                rmbs.append((tab, section, display_name, icon_path))
            if icon_path is None:
                icon = None
            else:
                icon = QIcon(icon_path)

            def callback(event, session=self.session, name=name, bundle_info=bundle_info, display_name=display_name):
                bundle_info.run_provider(session, name, session.toolbar, display_name=display_name)
            self.ttb.add_button(
                    tab, section, display_name, callback,
                    icon, description, **kw)
        self.ttb.show_tab('Home')
        self._set_right_mouse_button('init', self.session.ui.mouse_modes.mode("right", exact=True))

    def _set_right_mouse_button(self, trigger_name, mode):
        # highlight current right mouse button
        name = mode.name if mode is not None else None
        if name == self.current_right_mouse_button:
            return

        set_sections = set()
        has_button = name in self.right_mouse_buttons
        if has_button:
            for info in self.right_mouse_buttons[name]:
                tab_title, section_title, _, _ = info
                set_sections.add((tab_title, section_title))

        if self.current_right_mouse_button is not None:
            # remove highlighting
            for info in self.right_mouse_buttons[self.current_right_mouse_button]:
                tab_title, section_title, button_title, icon_path = info
                redo = (tab_title, section_title) not in set_sections
                self.ttb.remove_button_highlight(tab_title, section_title, button_title, redo=redo)
        if not has_button:
            return
        # highlight button(s)
        self.current_right_mouse_button = name
        for info in self.right_mouse_buttons[name]:
            tab_title, section_title, button_title, icon_path = info
            self.ttb.add_button_highlight(tab_title, section_title, button_title)
Exemplo n.º 12
0
class CommandLine(ToolInstance):

    SESSION_ENDURING = True

    show_history_label = "Command History..."
    compact_label = "Remove duplicate consecutive commands"
    help = "help:user/tools/cli.html"

    def __init__(self, session, tool_name):
        ToolInstance.__init__(self, session, tool_name)

        self._in_init = True
        from .settings import settings
        self.settings = settings
        from chimerax.ui import MainToolWindow
        self.tool_window = MainToolWindow(self,
                                          close_destroys=False,
                                          hide_title_bar=True)
        parent = self.tool_window.ui_area
        self.tool_window.fill_context_menu = self.fill_context_menu
        self.history_dialog = _HistoryDialog(self, self.settings.typed_only)
        from PyQt5.QtWidgets import QComboBox, QHBoxLayout, QLabel
        label = QLabel(parent)
        label.setText("Command:")

        class CmdText(QComboBox):
            def __init__(self, parent, tool):
                self.tool = tool
                QComboBox.__init__(self, parent)
                self._processing_key = False
                from PyQt5.QtCore import Qt
                # defer context menu to parent
                self.setContextMenuPolicy(Qt.NoContextMenu)
                self.setAcceptDrops(True)
                self._out_selection = None

            def dragEnterEvent(self, event):
                if event.mimeData().text():
                    event.acceptProposedAction()

            def dropEvent(self, event):
                text = event.mimeData().text()
                if text.startswith("file://"):
                    text = text[7:]
                self.lineEdit().insert(text)
                event.acceptProposedAction()

            def focusInEvent(self, event):
                self._out_selection = None
                QComboBox.focusInEvent(self, event)

            def focusOutEvent(self, event):
                le = self.lineEdit()
                self._out_selection = (sel_start, sel_length,
                                       txt) = (le.selectionStart(),
                                               len(le.selectedText()),
                                               le.text())
                QComboBox.focusOutEvent(self, event)
                if sel_start >= 0:
                    le.setSelection(sel_start, sel_length)

            def keyPressEvent(self, event, forwarded=False):
                self._processing_key = True
                from PyQt5.QtCore import Qt
                from PyQt5.QtGui import QKeySequence

                if session.ui.key_intercepted(event.key()):
                    return

                want_focus = forwarded and event.key() not in [
                    Qt.Key_Control, Qt.Key_Shift, Qt.Key_Meta, Qt.Key_Alt
                ]
                import sys
                control_key = Qt.MetaModifier if sys.platform == "darwin" else Qt.ControlModifier
                shifted = event.modifiers() & Qt.ShiftModifier
                if event.key() == Qt.Key_Up:  # up arrow
                    self.tool.history_dialog.up(shifted)
                elif event.key() == Qt.Key_Down:  # down arrow
                    self.tool.history_dialog.down(shifted)
                elif event.matches(QKeySequence.Undo):
                    want_focus = False
                    session.undo.undo()
                elif event.matches(QKeySequence.Redo):
                    want_focus = False
                    session.undo.redo()
                elif event.modifiers() & control_key:
                    if event.key() == Qt.Key_N:
                        self.tool.history_dialog.down(shifted)
                    elif event.key() == Qt.Key_P:
                        self.tool.history_dialog.up(shifted)
                    elif event.key() == Qt.Key_U:
                        self.tool.cmd_clear()
                        self.tool.history_dialog.search_reset()
                    elif event.key() == Qt.Key_K:
                        self.tool.cmd_clear_to_end_of_line()
                        self.tool.history_dialog.search_reset()
                    else:
                        QComboBox.keyPressEvent(self, event)
                else:
                    QComboBox.keyPressEvent(self, event)
                if want_focus:
                    # Give command line the focus, so that up/down arrow work as
                    # expected rather than changing the selection level
                    self.setFocus()
                self._processing_key = False

            def sizeHint(self):
                # prevent super-long commands from making the whole interface super wide
                return self.minimumSizeHint()

        self.text = CmdText(parent, self)
        self.text.setEditable(True)
        self.text.setCompleter(None)

        def sel_change_correction():
            # don't allow selection to change while focus is out
            if self.text._out_selection is not None:
                start, length, text = self.text._out_selection
                le = self.text.lineEdit()
                if text != le.text():
                    self.text._out_selection = (le.selectionStart(),
                                                len(le.selectedText()),
                                                le.text())
                    return
                if start >= 0 and (start, length) != (le.selectionStart(),
                                                      len(le.selectedText())):
                    le.setSelection(start, length)

        self.text.lineEdit().selectionChanged.connect(sel_change_correction)

        # pastes can have a trailing newline, which is problematic when appending to the pasted command...
        def strip_trailing_newlines():
            le = self.text.lineEdit()
            while le.text().endswith('\n'):
                le.setText(le.text()[:-1])

        self.text.lineEdit().textEdited.connect(strip_trailing_newlines)
        self.text.lineEdit().textEdited.connect(
            self.history_dialog.search_reset)

        def text_change(*args):
            # if text changes while focus is out, remember new selection
            if self.text._out_selection is not None:
                le = self.text.lineEdit()
                self.text._out_selection = (le.selectionStart(),
                                            len(le.selectedText()), le.text())

        self.text.lineEdit().selectionChanged.connect(text_change)
        layout = QHBoxLayout(parent)
        layout.setSpacing(1)
        layout.setContentsMargins(2, 0, 0, 0)
        layout.addWidget(label)
        layout.addWidget(self.text, 1)
        parent.setLayout(layout)
        # lineEdit() seems to be None during entire CmdText constructor, so connect here...
        self.text.lineEdit().returnPressed.connect(self.execute)
        self.text.currentTextChanged.connect(self.text_changed)
        self.text.forwarded_keystroke = lambda e: self.text.keyPressEvent(
            e, forwarded=True)
        session.ui.register_for_keystrokes(self.text)
        self.history_dialog.populate()
        self._just_typed_command = None
        self._command_started_handler = session.triggers.add_handler(
            "command started", self._command_started_cb)
        self.tool_window.manage(placement="bottom")
        self._in_init = False
        self._processing_command = False
        if self.settings.startup_commands:
            # prevent the startup command output from being summarized into 'startup messages' table
            session.ui.triggers.add_handler('ready',
                                            self._run_startup_commands)

    def cmd_clear(self):
        self.text.lineEdit().clear()

    def cmd_clear_to_end_of_line(self):
        le = self.text.lineEdit()
        t = le.text()[:le.cursorPosition()]
        le.setText(t)

    def cmd_replace(self, cmd):
        line_edit = self.text.lineEdit()
        line_edit.setText(cmd)
        line_edit.setCursorPosition(len(cmd))

    def delete(self):
        self.session.ui.deregister_for_keystrokes(self.text)
        self.session.triggers.remove_handler(self._command_started_handler)
        super().delete()

    def fill_context_menu(self, menu, x, y):
        # avoid having actions destroyed when this routine returns
        # by stowing a reference in the menu itself
        from PyQt5.QtWidgets import QAction
        filter_action = QAction("Typed Commands Only", menu)
        filter_action.setCheckable(True)
        filter_action.setChecked(self.settings.typed_only)
        filter_action.toggled.connect(
            lambda arg, f=self._set_typed_only: f(arg))
        menu.addAction(filter_action)
        select_action = QAction("Leave Failed Command Highlighted", menu)
        select_action.setCheckable(True)
        select_action.setChecked(self.settings.select_failed)
        select_action.toggled.connect(
            lambda arg, f=self._set_select_failed: f(arg))
        menu.addAction(select_action)

    def on_combobox(self, event):
        val = self.text.GetValue()
        if val == self.show_history_label:
            self.cmd_clear()
            self.history_dialog.window.shown = True
        elif val == self.compact_label:
            self.cmd_clear()
            prev_cmd = None
            unique_cmds = []
            for cmd in self.history_dialog._history:
                if cmd != prev_cmd:
                    unique_cmds.append(cmd)
                    prev_cmd = cmd
            self.history_dialog._history.replace(unique_cmds)
            self.history_dialog.populate()
        else:
            event.Skip()

    def text_changed(self, text):
        if text == self.show_history_label:
            self.cmd_clear()
            if not self._in_init:
                self.history_dialog.window.shown = True
        elif text == self.compact_label:
            self.cmd_clear()
            prev_cmd = None
            unique_cmds = []
            for cmd in self.history_dialog._history:
                if cmd != prev_cmd:
                    unique_cmds.append(cmd)
                    prev_cmd = cmd
            self.history_dialog._history.replace(unique_cmds)
            self.history_dialog.populate()

    def execute(self):
        from contextlib import contextmanager

        @contextmanager
        def processing_command(line_edit, cmd_text, command_worked,
                               select_failed):
            line_edit.blockSignals(True)
            self._processing_command = True
            # as per the docs for contextmanager, the yield needs
            # to be in a try/except if the exit code is to execute
            # after errors
            try:
                yield
            finally:
                line_edit.blockSignals(False)
                line_edit.setText(cmd_text)
                if command_worked[0] or select_failed:
                    line_edit.selectAll()
                self._processing_command = False

        session = self.session
        logger = session.logger
        text = self.text.lineEdit().text()
        logger.status("")
        from chimerax.core import errors
        from chimerax.core.commands import Command
        from html import escape
        for cmd_text in text.split("\n"):
            if not cmd_text:
                continue
            # don't select the text if the command failed, so that
            # an accidental keypress won't erase the command, which
            # probably needs to be edited to work
            command_worked = [False]
            with processing_command(self.text.lineEdit(), cmd_text,
                                    command_worked,
                                    self.settings.select_failed):
                try:
                    self._just_typed_command = cmd_text
                    cmd = Command(session)
                    cmd.run(cmd_text)
                    command_worked[0] = True
                except SystemExit:
                    # TODO: somehow quit application
                    raise
                except errors.UserError as err:
                    logger.status(str(err), color="crimson")
                    from chimerax.core.logger import error_text_format
                    logger.info(error_text_format % escape(str(err)),
                                is_html=True)
                except BaseException:
                    raise
        self.set_focus()

    def set_focus(self):
        from PyQt5.QtCore import Qt
        self.text.lineEdit().setFocus(Qt.OtherFocusReason)

    @classmethod
    def get_singleton(cls, session, **kw):
        from chimerax.core import tools
        return tools.get_singleton(session, CommandLine,
                                   'Command Line Interface', **kw)

    def _command_started_cb(self, trig_name, cmd_text):
        # the self._processing_command test is necessary when multiple commands
        # separated by semicolons are typed in order to prevent putting the
        # second and later commands into the command history, since we will get
        # triggers for each command in the line
        if self._just_typed_command or not self._processing_command:
            self.history_dialog.add(self._just_typed_command or cmd_text,
                                    typed=self._just_typed_command is not None)
            self.text.lineEdit().selectAll()
            self._just_typed_command = None

    def _run_startup_commands(self, *args):
        # log the commands; but prevent them from going into command history...
        self._processing_command = True
        from chimerax.core.commands import run
        from chimerax.core.errors import UserError
        try:
            for cmd_text in self.settings.startup_commands:
                run(self.session, cmd_text)
        except UserError as err:
            self.session.logger.status(
                "Error running startup command '%s': %s" %
                (cmd_text, str(err)),
                color="crimson",
                log=True)
        except Exception:
            self._processing_command = False
            raise
        self._processing_command = False

    def _set_select_failed(self, select_failed):
        self.settings.select_failed = select_failed

    def _set_typed_only(self, typed_only):
        self.settings.typed_only = typed_only
        self.history_dialog.set_typed_only(typed_only)
Exemplo n.º 13
0
    def __init__(self,
                 session,
                 tool_name,
                 value_name,
                 title,
                 value_range=(1, 10),
                 loop=True,
                 pause_frames=50,
                 pause_when_recording=True,
                 movie_filename='movie.mp4',
                 movie_framerate=25,
                 placement='side'):
        ToolInstance.__init__(self, session, tool_name)

        self.value_range = value_range
        self.loop = loop
        self.pause_frames = pause_frames
        self.pause_when_recording = pause_when_recording
        self._pause_count = 0
        self.movie_filename = movie_filename
        self.movie_framerate = movie_framerate
        self._last_shown_value = None

        self._play_handler = None
        self.recording = False
        self._block_update = False

        self.display_name = title  # Text shown on panel title-bar

        from chimerax.ui import MainToolWindow
        tw = MainToolWindow(self)
        self.tool_window = tw
        parent = tw.ui_area

        from PyQt5.QtWidgets import QHBoxLayout, QLabel, QSpinBox, QSlider, QPushButton
        from PyQt5.QtGui import QPixmap, QIcon
        from PyQt5.QtCore import Qt
        layout = QHBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(4)
        vl = QLabel(value_name)
        layout.addWidget(vl)
        self.value_box = vb = QSpinBox()
        vb.setRange(value_range[0], value_range[1])
        vb.valueChanged.connect(self.value_changed_cb)
        layout.addWidget(vb)
        self.slider = sl = QSlider(Qt.Horizontal)
        sl.setRange(value_range[0], value_range[1])
        sl.valueChanged.connect(self.slider_moved_cb)
        layout.addWidget(sl)
        self.play_button = pb = QPushButton()
        pb.setCheckable(True)
        pb.pressed.connect(self.play_cb)
        layout.addWidget(pb)
        self.record_button = rb = QPushButton()
        rb.setCheckable(True)
        rb.clicked.connect(self.record_cb)
        layout.addWidget(rb)
        parent.setLayout(layout)

        self.set_button_icon(play=True, record=True)

        tw.manage(placement=placement)
Exemplo n.º 14
0
class ModellerResultsViewer(ToolInstance):
    """ Viewer displays the models/results generated by Modeller"""

    help = "help:user/tools/modeller.html#output"

    def __init__(self, session, tool_name, models=None, attr_names=None):
        """ if 'models' is None, then we are being restored from a session and
            set_state_from_snapshot will be called later.
        """

        ToolInstance.__init__(self, session, tool_name)
        if models is None:
            return
        self._finalize_init(session, models, attr_names)

    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 delete(self):
        for handler in self.handlers:
            handler.remove()
        self.row_item_lookup = {}
        ToolInstance.delete(self)

    def fetch_additional_scores(self, refresh=False):
        self.scores_fetched = True
        from chimerax.core.commands import run, concise_model_spec
        run(
            self.session,
            "modeller scores %s refresh %s" % (concise_model_spec(
                self.session, self.models), str(refresh).lower()))

    def fill_context_menu(self, menu, x, y):
        from PyQt5.QtWidgets import QAction
        if self.scores_fetched:
            refresh_action = QAction("Refresh Scores", menu)
            refresh_action.triggered.connect(
                lambda arg: self.fetch_additional_scores(refresh=True))
            menu.addAction(refresh_action)
        else:
            fetch_action = QAction("Fetch Additional Scores", menu)
            fetch_action.triggered.connect(
                lambda arg: self.fetch_additional_scores())
            menu.addAction(fetch_action)

    @classmethod
    def restore_snapshot(cls, session, data):
        inst = super().restore_snapshot(session, data['ToolInstance'])
        inst._finalize_init(session, data['models'], data['attr_names'],
                            data['scores_fetched'])
        return inst

    SESSION_SAVE = True

    def take_snapshot(self, session, flags):
        data = {
            'ToolInstance': ToolInstance.take_snapshot(self, session, flags),
            'models': self.models,
            'attr_names': self.attr_names,
            'scores_fetched': self.scores_fetched
        }
        return data

    def _changes_cb(self, trig_name, trig_data):
        structure, changes_obj = trig_data
        need_update = False
        if changes_obj.modified_structures():
            for reason in changes_obj.structure_reasons():
                if reason.startswith("modeller_") and reason.endswith(
                        " changed"):
                    need_update = True
                    attr_name = reason[:-8]
                    if attr_name not in self.attr_names:
                        self.attr_names.append(attr_name)
        if need_update:
            # atomic changes are already collated, so don't need to delay table update
            self._fill_table()

    def _fill_table(self, *args):
        self.table.clear()
        self.table.setColumnCount(len(self.attr_names) + 1)
        self.table.setHorizontalHeaderLabels(
            ["Model"] +
            [attr_name[9:].replace('_', ' ') for attr_name in self.attr_names])
        self.table.setRowCount(len(self.models))
        from PyQt5.QtWidgets import QTableWidgetItem
        for row, m in enumerate(self.models):
            item = QTableWidgetItem('#' + m.id_string)
            self.table.setItem(row, 0, item)
            for c, attr_name in enumerate(self.attr_names):
                self.table.setItem(
                    row, c + 1,
                    QTableWidgetItem("%g" % getattr(m, attr_name) if hasattr(
                        m, attr_name) else ""))
        for i in range(self.table.columnCount()):
            self.table.resizeColumnToContents(i)

    def _models_removed_cb(self, *args):
        remaining = [m for m in self.models if m.id is not None]
        if remaining == self.models:
            return
        self.models = remaining
        if not self.models:
            self.delete()
        else:
            self._fill_table()

    def _table_selection_cb(self):
        rows = set([index.row() for index in self.table.selectedIndexes()])
        sel_ids = set([self.table.item(r, 0).text() for r in rows])
        for m in self.models:
            m.display = ('#' + m.id_string) in sel_ids or not sel_ids
Exemplo n.º 15
0
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)
Exemplo n.º 16
0
class ModellerLauncher(ToolInstance):
    """Generate the inputs needed by Modeller for comparative modeling"""

    help = "help:user/tools/modeller.html"
    SESSION_SAVE = False

    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,
                                          statusbar=False)
        parent = self.tool_window.ui_area

        from PyQt5.QtWidgets import QListWidget, QFormLayout, QAbstractItemView, QGroupBox, QVBoxLayout
        from PyQt5.QtWidgets import QDialogButtonBox as qbbox
        interface_layout = QVBoxLayout()
        interface_layout.setContentsMargins(0, 0, 0, 0)
        interface_layout.setSpacing(0)
        parent.setLayout(interface_layout)
        alignments_area = QGroupBox("Sequence alignments")
        interface_layout.addWidget(alignments_area)
        interface_layout.setStretchFactor(alignments_area, 1)
        alignments_layout = QVBoxLayout()
        alignments_layout.setContentsMargins(0, 0, 0, 0)
        alignments_area.setLayout(alignments_layout)
        self.alignment_list = AlignmentListWidget(session)
        self.alignment_list.setSelectionMode(
            QAbstractItemView.ExtendedSelection)
        self.alignment_list.keyPressEvent = session.ui.forward_keystroke
        self.alignment_list.value_changed.connect(self._list_selection_cb)
        self.alignment_list.alignments_changed.connect(
            self._update_sequence_menus)
        alignments_layout.addWidget(self.alignment_list)
        alignments_layout.setStretchFactor(self.alignment_list, 1)
        targets_area = QGroupBox("Target sequences")
        self.targets_layout = QFormLayout()
        targets_area.setLayout(self.targets_layout)
        interface_layout.addWidget(targets_area)
        self.seq_menu = {}
        self._update_sequence_menus(session.alignments.alignments)
        options_area = QGroupBox("Options")
        options_layout = QVBoxLayout()
        options_layout.setContentsMargins(0, 0, 0, 0)
        options_area.setLayout(options_layout)
        interface_layout.addWidget(options_area)
        interface_layout.setStretchFactor(options_area, 2)
        from chimerax.ui.options import CategorizedSettingsPanel, BooleanOption, IntOption, PasswordOption, \
            OutputFolderOption
        panel = CategorizedSettingsPanel(category_sorting=False, buttons=False)
        options_layout.addWidget(panel)
        from .settings import get_settings
        settings = get_settings(session)
        panel.add_option(
            "Basic",
            BooleanOption(
                "Make multichain model from multichain template",
                settings.multichain,
                None,
                balloon=
                "If false, all chains (templates) associated with an alignment will be used in combination\n"
                "to model the target sequence of that alignment, i.e. a monomer will be generated from the\n"
                "alignment.  If true, the target sequence will be modeled from each template, i.e. a multimer\n"
                "will be generated from the alignment (assuming multiple chains are associated).",
                attr_name="multichain",
                settings=settings))
        max_models = 1000
        panel.add_option(
            "Basic",
            IntOption(
                "Number of models",
                settings.num_models,
                None,
                attr_name="num_models",
                settings=settings,
                min=1,
                max=max_models,
                balloon=
                "Number of model structures to generate.  Must be no more than %d.\n"
                "Warning: please consider the calculation time" % max_models))
        key = "" if settings.license_key is None else settings.license_key
        panel.add_option(
            "Basic",
            PasswordOption(
                "Modeller license key",
                key,
                None,
                attr_name="license_key",
                settings=settings,
                balloon=
                "Your Modeller license key.  You can obtain a license key by registering at the Modeller web site"
            ))
        panel.add_option(
            "Advanced",
            BooleanOption(
                "Use fast/approximate mode (produces only one model)",
                settings.fast,
                None,
                attr_name="fast",
                settings=settings,
                balloon=
                "If enabled, use a fast approximate method to generate a single model.\n"
                "Typically use to get a rough idea what the model will look like or\n"
                "to check that the alignment is reasonable."))
        panel.add_option(
            "Advanced",
            BooleanOption(
                "Include non-water HETATM residues from template",
                settings.het_preserve,
                None,
                attr_name="het_preserve",
                settings=settings,
                balloon=
                "If enabled, all non-water HETATM residues in the template\n"
                "structure(s) will be transferred into the generated models."))
        panel.add_option(
            "Advanced",
            BooleanOption(
                "Build models with hydrogens",
                settings.hydrogens,
                None,
                attr_name="hydrogens",
                settings=settings,
                balloon=
                "If enabled, the generated models will include hydrogen atoms.\n"
                "Otherwise, only heavy atom coordinates will be built.\n"
                "Increases computation time by approximately a factor of 4."))
        panel.add_option(
            "Advanced",
            OutputFolderOption(
                "Temporary folder location (optional)",
                settings.temp_path,
                None,
                attr_name="temp_path",
                settings=settings,
                balloon=
                "Specify a folder for temporary files.  If not specified,\n"
                "a location will be generated automatically."))
        panel.add_option(
            "Advanced",
            BooleanOption(
                "Include water molecules from template",
                settings.water_preserve,
                None,
                attr_name="water_preserve",
                settings=settings,
                balloon="If enabled, all water molecules in the template\n"
                "structure(s) will be included in the generated models."))
        from PyQt5.QtCore import Qt
        from chimerax.ui.widgets import Citation
        interface_layout.addWidget(Citation(
            session, "A. Sali and T.L. Blundell.\n"
            "Comparative protein modelling by satisfaction of spatial restraints.\n"
            "J. Mol. Biol. 234, 779-815, 1993.",
            prefix="Publications using Modeller results should cite:",
            pubmed_id=18428767),
                                   alignment=Qt.AlignCenter)
        bbox = qbbox(qbbox.Ok | qbbox.Cancel | qbbox.Help)
        bbox.accepted.connect(self.launch_modeller)
        bbox.rejected.connect(self.delete)
        from chimerax.core.commands import run
        bbox.helpRequested.connect(
            lambda run=run, ses=session: run(ses, "help " + self.help))
        interface_layout.addWidget(bbox)
        self.tool_window.manage(None)

    def delete(self):
        ToolInstance.delete(self)

    def launch_modeller(self):
        from chimerax.core.commands import run, FileNameArg, StringArg
        from chimerax.core.errors import UserError
        alignments = self.alignment_list.value
        if not alignments:
            raise UserError("No alignments chosen for modeling")
        aln_seq_args = []
        for aln in alignments:
            seq_menu = self.seq_menu[aln]
            seq = seq_menu.value
            if not seq:
                raise UserError("No target sequence chosen for alignment %s" %
                                aln.ident)
            aln_seq_args.append(
                StringArg.unparse("%s:%d" %
                                  (aln.ident, aln.seqs.index(seq) + 1)))
        from .settings import get_settings
        settings = get_settings(self.session)
        run(
            self.session,
            "modeller comparative %s multichain %s numModels %d fast %s hetPreserve %s"
            " hydrogens %s%s waterPreserve %s" %
            (" ".join(aln_seq_args), repr(
                settings.multichain).lower(), settings.num_models,
             repr(settings.fast).lower(), repr(settings.het_preserve).lower(),
             repr(settings.hydrogens).lower(), " tempPath %s" %
             FileNameArg.unparse(settings.temp_path) if settings.temp_path else
             "", repr(settings.water_preserve).lower()))
        self.delete()

    def _list_selection_cb(self):
        layout = self.targets_layout
        while layout.count() > 0:
            item = layout.takeAt(0)
            if not item:
                break
            widget = item.widget()
            if isinstance(widget, AlignSeqMenuButton):
                widget.setHidden(True)
            else:
                widget.deleteLater()
        for sel_aln in self.alignment_list.value:
            mb = self.seq_menu[sel_aln]
            mb.setHidden(False)
            layout.addRow(sel_aln.ident, mb)

    def _update_sequence_menus(self, alignments):
        alignment_set = set(alignments)
        for aln, mb in list(self.seq_menu.items()):
            if aln not in alignment_set:
                row, role = self.targets_layout.getWidgetPosition(mb)
                if row >= 0:
                    self.targets_layout.removeRow(row)
                del self.seq_menu[aln]
        for aln in alignments:
            if aln not in self.seq_menu:
                self.seq_menu[aln] = AlignSeqMenuButton(
                    aln, no_value_button_text="No target chosen")
Exemplo n.º 17
0
    def __init__(self, session, tool_name):
        ToolInstance.__init__(self, session, tool_name)

        self.display_name = 'Marker Placement'

        from chimerax.ui import MainToolWindow
        tw = MainToolWindow(self, close_destroys=False)
        self.tool_window = tw
        parent = tw.ui_area
        
        from PyQt5.QtWidgets import QFrame, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QLineEdit, QSizePolicy, QCheckBox

        playout = QVBoxLayout(parent)
        playout.setContentsMargins(0,0,0,0)
        playout.setSpacing(0)
        parent.setLayout(playout)

        f = QFrame(parent)
        f.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)
        playout.addWidget(f)
        layout = QVBoxLayout(f)
        layout.setContentsMargins(0,0,0,0)
        layout.setSpacing(0)
        f.setLayout(layout)
        
        # Marker and link color and radius
        mf = QFrame(f)
        mf.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)
        layout.addWidget(mf)
        mm_layout = QHBoxLayout(mf)
        mm_layout.setContentsMargins(0,0,0,0)
        mm_layout.setSpacing(5)
        mf.setLayout(mm_layout)

        ml = QLabel('Marker color', mf)
        mm_layout.addWidget(ml)
        from chimerax.ui.widgets import ColorButton
        self._marker_color = mc = ColorButton(mf, max_size = (16,16))
        mc.color_changed.connect(self._marker_color_changed)
        mm_layout.addWidget(mc)
        rl = QLabel(' radius', mf)
        mm_layout.addWidget(rl)
        self._marker_radius = mr = QLineEdit('', mf)
        mr.setMaximumWidth(40)
        mr.returnPressed.connect(self._marker_radius_changed)
        mm_layout.addWidget(mr)

        mm_layout.addSpacing(20)
        ml = QLabel('Link color', mf)
        mm_layout.addWidget(ml)
        from chimerax.ui.widgets import ColorButton
        self._link_color = lc = ColorButton(mf, max_size = (16,16))
        lc.color_changed.connect(self._link_color_changed)
        mm_layout.addWidget(lc)
        rl = QLabel(' radius', mf)
        mm_layout.addWidget(rl)
        self._link_radius = lr = QLineEdit('', mf)
        lr.setMaximumWidth(40)
        lr.returnPressed.connect(self._link_radius_changed)
        mm_layout.addWidget(lr)

        mm_layout.addStretch(1)    # Extra space at end

        # Link consecutive markers checkbutton
        self.link_new_button = lm = QCheckBox('Link new marker to selected marker', f)
        lm.stateChanged.connect(self.link_new_cb)
        layout.addWidget(lm)

        layout.addSpacing(5)
        hl = QLabel('Place markers using mouse modes in the Markers toolbar')
        layout.addWidget(hl)

        self.update_settings()
        
        tw.manage(placement="side")
Exemplo n.º 18
0
class EnergyPlot(ToolInstance):
    SESSION_ENDURING = False
    SESSION_SAVE = False

    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 _build_ui(self):
        layout = QGridLayout()

        self.figure = Figure(figsize=(2, 2))
        self.canvas = Canvas(self.figure)

        ax = self.figure.add_axes((0.15, 0.20, 0.80, 0.70))

        fr = self.filereader
        if fr.all_geom is None:
            self.opened = False
            return

        data = []
        for step in fr.all_geom:
            info = [
                item for item in step
                if isinstance(item, dict) and "energy" in item
            ]
            if len(info) < 1:
                #we will be unable to load an enegy plot because some structure does not have an associated energy
                self.opened = False
                self.session.logger.error(
                    "not enough iterations to plot - %i found" % len(info))
                return
            else:
                info = info[0]
            data.append(info["energy"])

        self.ys = data

        se = np.ptp(data)

        self.nrg_plot = ax.plot(self.structure.coordset_ids,
                                data,
                                marker='o',
                                c='gray',
                                markersize=3)
        self.nrg_plot = self.nrg_plot[0]
        ax.set_xlabel('iteration')
        ax.set_ylabel(r'energy ($E_h$)')
        ax.set_ylim(bottom=(min(data) - se / 10), top=(max(data) + se / 10))

        minlocs = [
            self.structure.coordset_ids[i]
            for i in range(0, self.structure.num_coordsets)
            if data[i] == min(data)
        ]
        mins = [min(data) for m in minlocs]

        maxlocs = [
            self.structure.coordset_ids[i]
            for i in range(0, self.structure.num_coordsets)
            if data[i] == max(data)
        ]
        maxs = [max(data) for m in minlocs]

        ax.plot(minlocs, mins, marker='*', c='blue', markersize=5)
        ax.plot(maxlocs, maxs, marker='*', c='red', markersize=5)

        ax.ticklabel_format(axis='y',
                            style='sci',
                            scilimits=(0, 0),
                            useOffset=True)
        ax.ticklabel_format(axis='x', style='plain', useOffset=False)

        self.canvas.mpl_connect('button_release_event', self.unclick)
        self.canvas.mpl_connect('button_press_event', self.onclick)
        self.canvas.mpl_connect('motion_notify_event', self.drag)
        self.canvas.mpl_connect('scroll_event', self.zoom)

        self.annotation = ax.annotate("",
                                      xy=(0, 0),
                                      xytext=(0, 10),
                                      textcoords="offset points",
                                      fontfamily='Arial')
        self.annotation.set_visible(False)

        ax.autoscale()
        self.canvas.draw()
        self.canvas.setMinimumWidth(400)
        self.canvas.setMinimumHeight(200)
        layout.addWidget(self.canvas)

        toolbar_widget = QWidget()
        toolbar = NavigationToolbar(self.canvas, toolbar_widget)

        toolbar.setMaximumHeight(32)
        self.toolbar = toolbar
        layout.addWidget(toolbar)

        #menu bar for saving stuff
        menu = QMenuBar()
        file = menu.addMenu("&Export")
        file.addAction("&Save CSV...")

        file.triggered.connect(self.save)

        menu.setNativeMenuBar(False)
        self._menu = menu
        layout.setMenuBar(menu)

        self.tool_window.ui_area.setLayout(layout)

        self.tool_window.manage(None)

        self.opened = True

    def save(self):
        filename, _ = QFileDialog.getSaveFileName(filter="CSV Files (*.csv)")
        if filename:
            s = "iteration,energy\n"
            for i, nrg in enumerate(self.ys):
                s += "%i,%f\n" % (self.structure.coordset_ids[i], nrg)

            with open(filename, 'w') as f:
                f.write(s.strip())

            print("saved to %s" % filename)

    def zoom(self, event):
        if event.xdata is None:
            return
        a = self.figure.gca()
        x0, x1 = a.get_xlim()
        x_range = x1 - x0
        xdiff = -0.05 * event.step * x_range
        xshift = 0.2 * (event.xdata - (x0 + x1) / 2)
        nx0 = x0 - xdiff + xshift
        nx1 = x1 + xdiff + xshift

        y0, y1 = a.get_ylim()
        y_range = y1 - y0
        ydiff = -0.05 * event.step * y_range
        yshift = 0.2 * (event.ydata - (y0 + y1) / 2)
        ny0 = y0 - ydiff + yshift
        ny1 = y1 + ydiff + yshift

        a.set_xlim(nx0, nx1)
        a.set_ylim(ny0, ny1)
        self.canvas.draw()

    def move(self, dx, dy):
        win = self.tool_window.ui_area
        w, h = win.width(), win.height()
        if w == 0 or h == 0:
            return
        a = self.figure.gca()
        x0, x1 = a.get_xlim()
        xs = dx / w * (x1 - x0)
        nx0, nx1 = x0 - xs, x1 - xs
        y0, y1 = a.get_ylim()
        ys = dy / h * (y1 - y0)
        ny0, ny1 = y0 - ys, y1 - ys
        a.set_xlim(nx0, nx1)
        a.set_ylim(ny0, ny1)
        self.canvas.draw()

    def onclick(self, event):
        if self.toolbar.mode != "":
            return

        self.press = event.x, event.y, event.xdata, event.ydata

    def unclick(self, event):
        if self.toolbar.mode != "":
            return

        modifiers = keyboardModifiers()
        #matplotlib's mouse event never sets the 'key' attribute for me
        #That's fine.
        #I'll just get my key presses from pyqt5.
        if modifiers != Qt.NoModifier and event.button == 1:
            a = self.figure.gca()
            a.autoscale()
            self.canvas.draw()

        elif not self.dragging and event.button == 1 and event.key is None:
            self.change_coordset(event)

        self.press = None
        self.drag_prev = None
        self.dragging = False

    def change_coordset(self, event):
        if event.xdata is not None:
            x = int(round(event.xdata))
            if x > self.structure.num_coordsets:
                self.structure.active_coordset_id = self.structure.num_coordsets
            elif x < 1:
                self.structure.active_coordset_id = 1
            else:
                self.structure.active_coordset_id = x

    def update_label(self, ndx):
        self.annotation.xy = (self.structure.coordset_ids[ndx], self.ys[ndx])
        if self.ys[ndx] == max(self.ys):
            self.annotation.set_text("%s (maxima)" % repr(self.ys[ndx]))
        elif self.ys[ndx] == min(self.ys):
            self.annotation.set_text("%s (minima)" % repr(self.ys[ndx]))
        else:
            self.annotation.set_text(repr(self.ys[ndx]))

    def drag(self, event):
        modifiers = keyboardModifiers()
        if self.toolbar.mode != "":
            return
        #matplotlib's mouse event never sets the 'key' attribute for me
        #That's fine.
        #I'll just get my key presses from pyqt5.
        elif modifiers != Qt.NoModifier:
            return
        elif event.button == 1:
            return self.change_coordset(event)
        elif self.press is None:
            vis = self.annotation.get_visible()
            on_item, ndx = self.nrg_plot.contains(event)
            if on_item:
                self.update_label(ndx['ind'][0])
                self.annotation.set_visible(True)
                self.canvas.draw()
            else:
                if vis:
                    self.annotation.set_visible(False)
                    self.canvas.draw()
            return
        elif event.button != 2:
            return

        x, y, xpress, ypress = self.press
        dx = event.x - x
        dy = event.y - y
        drag_dist = np.linalg.norm([dx, dy])
        if self.dragging or drag_dist >= self._min_drag or event.button == 2:
            self.dragging = True
            if self.drag_prev is not None:
                x, y, xpress, ypress = self.drag_prev
                dx = event.x - x
                dy = event.y - y

            self.drag_prev = event.x, event.y, event.xdata, event.ydata
            self.move(dx, dy)

    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 check_closed_models(self, name, models):
        if self.structure in models:
            self.delete()

    def delete(self):
        self.session.triggers.remove_handler(self._model_closed)

        super().delete()

    def close(self):
        self.session.triggers.remove_handler(self._model_closed)

        super().close()
Exemplo n.º 19
0
class bogusUI(ToolInstance):

    SIZE = (500, 25)

    _PageTemplate = """<html>
<head>
<title>Select Models</title>
<script>
function action(button) { window.location.href = "bogus:_action:" + button; }
</script>
<style>
.refresh { color: blue; font-size: 80%; font-family: monospace; }
</style>
</head>
<body>
<h2>Select Models
    <a href="bogus:_refresh" class="refresh">refresh</a></h2>
MODEL_SELECTION
<p>
ACTION_BUTTONS
</body>
</html>"""

    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 _make_page(self, *args):
        models = self.session.models
        from io import StringIO
        page = self._PageTemplate

        # Construct model selector
        s = StringIO()
        print("<select multiple=\"1\">", file=s)
        for model in models.list():
            name = '.'.join([str(n) for n in model.id])
            print("<option value=\"%s\">%s</option>" % (name, name), file=s)
        print("</select>", file=s)
        page = page.replace("MODEL_SELECTION", s.getvalue())

        # Construct action buttons
        s = StringIO()
        for action in ["BLAST"]:
            print("<button type=\"button\""
                  "onclick=\"action('%s')\">%s</button>" % (action, action),
                  file=s)
        page = page.replace("ACTION_BUTTONS", s.getvalue())

        # Update display
        self.webview.SetPage(page, "")

    def _on_navigating(self, event):
        session = self.session
        # Handle event
        url = event.GetURL()
        if url.startswith("bogus:"):
            event.Veto()
            parts = url.split(':')
            method = getattr(self, parts[1])
            args = parts[2:]
            method(session, *args)

    #
    # Callbacks from HTML
    #
    def _refresh(self, session):
        self._make_page()

    def _action(self, session, action):
        print("bogus action button clicked: %s" % action)
Exemplo n.º 20
0
    def __init__(self, session, tool_name):

        self._ses = session

        ToolInstance.__init__(self, session, tool_name)

        from .settings import BugReporterSettings
        self.settings = BugReporterSettings(session, 'Bug Reporter')

        from chimerax.ui import MainToolWindow
        tw = MainToolWindow(self)
        self.tool_window = tw
        parent = tw.ui_area
        parent.setMinimumWidth(600)

        from PyQt5.QtWidgets import QGridLayout, QLabel, QPushButton, QLineEdit, QTextEdit
        from PyQt5.QtWidgets import QWidget, QHBoxLayout, QCheckBox
        from PyQt5.QtCore import Qt

        layout = QGridLayout(parent)
        layout.setContentsMargins(3, 3, 3, 3)
        layout.setHorizontalSpacing(3)
        layout.setVerticalSpacing(3)
        parent.setLayout(layout)

        row = 1

        intro = '''
        <center><h1>Report a Bug</h1></center>
        <p>Thank you for using our feedback system.
      Feedback is greatly appreciated and plays a crucial role
      in the development of ChimeraX.</p>
      <p><b>Note</b>:
          We do not automatically collect any personal information or the data
          you were working with when the problem occurred.  Providing your e-mail address is optional,
          but will allow us to inform you of a fix or to ask questions, if needed.
          Attaching data may also be helpful.  However, any information or data
          you wish to keep confidential should be sent separately (not using this form).</p>
        '''
        il = QLabel(intro)
        il.setWordWrap(True)
        layout.addWidget(il, row, 1, 1, 2)
        row += 1

        cnl = QLabel('Contact Name:')
        cnl.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
        layout.addWidget(cnl, row, 1)
        self.contact_name = cn = QLineEdit(self.settings.contact_name)
        layout.addWidget(cn, row, 2)
        row += 1

        eml = QLabel('Email Address:')
        eml.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
        layout.addWidget(eml, row, 1)
        self.email_address = em = QLineEdit(self.settings.email_address)
        layout.addWidget(em, row, 2)
        row += 1

        class TextEdit(QTextEdit):
            def __init__(self, text, initial_line_height):
                self._lines = initial_line_height
                QTextEdit.__init__(self, text)

            def sizeHint(self):
                from PyQt5.QtCore import QSize
                fm = self.fontMetrics()
                h = self._lines * fm.lineSpacing() + fm.ascent()
                size = QSize(-1, h)
                return size

            def minimumSizeHint(self):
                from PyQt5.QtCore import QSize
                return QSize(1, 1)

        dl = QLabel('Description:')
        dl.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
        layout.addWidget(dl, row, 1)
        self.description = d = TextEdit('', 3)
        d.setText(
            '<font color=blue>(Describe the actions that caused this problem to occur here)</font>'
        )
        layout.addWidget(d, row, 2)
        row += 1

        gil = QLabel('Gathered Information:')
        gil.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
        layout.addWidget(gil, row, 1)
        self.gathered_info = gi = TextEdit('', 3)
        import sys
        info = self.opengl_info()
        if sys.platform == 'win32':
            info += _win32_info()
        elif sys.platform == 'linux':
            info += _linux_info()
        elif sys.platform == 'darwin':
            info += _darwin_info()
        info += _qt_info(session)
        info += _package_info()
        gi.setText(info)
        layout.addWidget(gi, row, 2)
        row += 1

        fal = QLabel('File Attachment:')
        fal.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
        layout.addWidget(fal, row, 1)
        fb = QWidget()
        layout.addWidget(fb, row, 2)
        fbl = QHBoxLayout(fb)
        fbl.setSpacing(3)
        fbl.setContentsMargins(0, 0, 0, 0)
        self.attachment = fa = QLineEdit('')
        fbl.addWidget(fa)
        fab = QPushButton('Browse')
        fab.clicked.connect(lambda e: self.file_browse())
        fbl.addWidget(fab)

        row += 1

        pl = QLabel('Platform:')
        pl.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
        layout.addWidget(pl, row, 1)
        from platform import platform
        self.platform = p = QLineEdit(platform())
        p.setReadOnly(True)
        layout.addWidget(p, row, 2)
        row += 1

        vl = QLabel('ChimeraX Version:')
        vl.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
        layout.addWidget(vl, row, 1)
        self.version = v = QLineEdit(self.chimerax_version())
        v.setReadOnly(True)
        layout.addWidget(v, row, 2)
        row += 1

        il = QWidget()
        layout.addWidget(il, row, 2)
        ilayout = QHBoxLayout(il)
        ilayout.setContentsMargins(0, 0, 0, 0)
        self.include_log = ilc = QCheckBox()
        ilc.setChecked(True)
        ilayout.addWidget(ilc)
        ill = QLabel('Include log contents in bug report')
        ill.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
        ilayout.addWidget(ill)
        ilayout.addStretch(1)
        row += 1

        self.result = rl = QLabel('')
        rl.setWordWrap(True)
        layout.addWidget(rl, row, 1, 1, 2)
        row += 1

        # Button row
        brow = QWidget()
        blayout = QHBoxLayout()
        blayout.setContentsMargins(0, 0, 0, 0)
        blayout.setSpacing(2)
        brow.setLayout(blayout)
        layout.addWidget(brow, row, 1, 1, 2)
        row += 1

        blayout.addStretch(1)  # Extra space at start of button row.

        self.submit_button = sb = QPushButton('Submit', brow)
        sb.clicked.connect(lambda e: self.submit())
        blayout.addWidget(sb)

        self.cancel_button = cb = QPushButton('Cancel', brow)
        cb.clicked.connect(lambda e: self.cancel())
        blayout.addWidget(cb)

        tw.manage(placement=None)  # Don't dock it to main window.
Exemplo n.º 21
0
class UpdateTool(ToolInstance):

    SESSION_ENDURING = True
    # if SESSION_ENDURING is True, tool instance not deleted at session closure
    help = "help:user/tools/updates.html"

    NAME_COLUMN = 0
    CURRENT_VERSION_COLUMN = 1
    NEW_VERSION_COLUMN = 2
    CATEGORY_COLUMN = 3
    NUM_COLUMNS = CATEGORY_COLUMN + 1

    def __init__(self, session, tool_name, dialog_type=None):
        if dialog_type is None:
            dialog_type = DialogType.ALL_AVAILABLE
        ToolInstance.__init__(self, session, tool_name)
        from chimerax.ui import MainToolWindow
        self.tool_window = MainToolWindow(self)
        parent = self.tool_window.ui_area

        from PyQt5.QtCore import Qt
        from PyQt5.QtWidgets import QTreeWidget, QHBoxLayout, QVBoxLayout, QAbstractItemView, \
            QPushButton, QLabel, QComboBox
        layout = QVBoxLayout()
        parent.setLayout(layout)
        label = QLabel(
            "<p>Select the individual bundles you want to install by checking the box."
            "  Then click on the <b>Install</b> button to install them."
            "  The default bundle version is the newest one, but you can select an older version."
            "<p>When only showing updates, check <b>All</b> to select all of them."
        )
        label.setWordWrap(True)
        layout.addWidget(label)
        choice_layout = QHBoxLayout()
        layout.addLayout(choice_layout)
        label = QLabel("<b>Show:</b>")
        choice_layout.addWidget(label)
        self.choice = QComboBox()
        choice_layout.addWidget(self.choice)
        for dt in DialogType:
            self.choice.addItem(dt.name.replace('_', ' ').title(), dt)
        self.choice.setCurrentIndex(self.choice.findData(dialog_type))
        self.choice.currentIndexChanged.connect(self.new_choice)
        choice_layout.addStretch()

        class SizedTreeWidget(QTreeWidget):
            def sizeHint(self):
                from PyQt5.QtCore import QSize
                width = self.header().length()
                return QSize(width, 200)

        self.updates = SizedTreeWidget()
        # self.updates = QTreeWidget(parent)
        layout.addWidget(self.updates)
        self.updates.setHeaderLabels([
            "Bundle\nName", "Current\nVersion", "Available\nVersion(s)",
            "Category"
        ])
        self.updates.setSortingEnabled(True)
        hi = self.updates.headerItem()
        hi.setTextAlignment(self.NAME_COLUMN, Qt.AlignCenter)
        hi.setTextAlignment(self.CURRENT_VERSION_COLUMN, Qt.AlignCenter)
        hi.setTextAlignment(self.NEW_VERSION_COLUMN, Qt.AlignCenter)

        self.updates.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.updates.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self.updates.setEditTriggers(QAbstractItemView.NoEditTriggers)
        buttons_layout = QHBoxLayout()
        layout.addLayout(buttons_layout)
        button = QPushButton("Help")
        button.clicked.connect(self.help_button)
        buttons_layout.addWidget(button)
        buttons_layout.addStretch()
        button = QPushButton("Install")
        button.clicked.connect(self.install)
        buttons_layout.addWidget(button)
        button = QPushButton("Cancel")
        button.clicked.connect(self.cancel)
        buttons_layout.addWidget(button)

        self._fill_updates()
        self.tool_window.fill_context_menu = self.fill_context_menu
        self.tool_window.manage(placement=None)

    def help_button(self):
        from chimerax.help_viewer import show_url
        show_url(self.session, self.help, new_tab=True)

    def cancel(self):
        self.session.ui.main_window.close_request(self.tool_window)

    def fill_context_menu(self, menu, x, y):
        from PyQt5.QtWidgets import QAction
        settings_action = QAction("Settings...", menu)
        settings_action.triggered.connect(lambda arg: self.show_settings())
        menu.addAction(settings_action)

    def show_settings(self):
        self.session.ui.main_window.show_settings('Toolshed')

    def _fill_updates(self):
        from PyQt5.QtCore import Qt
        from PyQt5.QtWidgets import QTreeWidgetItem, QComboBox
        from packaging.version import Version
        session = self.session
        toolshed = session.toolshed
        self.actions = []
        info = toolshed.bundle_info(session.logger,
                                    installed=False,
                                    available=True)
        dialog_type = self.choice.currentData()
        new_bundles = {}
        last_bundle_name = None
        installed_version = ""
        for available in info:
            if last_bundle_name is None or available.name != last_bundle_name:
                last_bundle_name = available.name
                installed_bi = toolshed.find_bundle(last_bundle_name,
                                                    session.logger)
                if installed_bi is not None:
                    installed_version = Version(installed_bi.version)
                else:
                    installed_version = ""
                    if dialog_type != DialogType.ALL_AVAILABLE:
                        continue
            elif not installed_version and dialog_type != DialogType.ALL_AVAILABLE:
                continue
            new_version = Version(available.version)
            if dialog_type == DialogType.UPDATES_ONLY:
                if new_version <= installed_version:
                    continue
            data = new_bundles.setdefault(
                last_bundle_name, ([], installed_version, available.synopsis,
                                   available.categories[0]))
            data[0].append(new_version)

        self.updates.clear()
        if not new_bundles:
            return
        if dialog_type != DialogType.UPDATES_ONLY:
            self.all_items = all_items = self.updates.invisibleRootItem()
        else:
            self.all_items = all_items = QTreeWidgetItem()
            all_items.setText(0, "All")
            all_items.setFlags(Qt.ItemIsEnabled | Qt.ItemIsUserCheckable
                               | Qt.ItemIsAutoTristate)
            all_items.setCheckState(self.NAME_COLUMN, Qt.Unchecked)
            self.updates.addTopLevelItem(all_items)
        self.updates.expandItem(all_items)
        flags = Qt.ItemIsEnabled | Qt.ItemIsUserCheckable | Qt.ItemIsSelectable | Qt.ItemNeverHasChildren
        for bundle_name in new_bundles:
            new_versions, installed_version, synopsis, category = new_bundles[
                bundle_name]
            item = QTreeWidgetItem(all_items)
            # Name column
            item.setData(self.NAME_COLUMN, Qt.UserRole, bundle_name)
            if False:
                # TODO: need delagate to show Qt Rich Text
                item.setText(self.NAME_COLUMN,
                             toolshed.bundle_link(bundle_name))
            else:
                if bundle_name.startswith("ChimeraX-"):
                    bundle_name = bundle_name[len("ChimeraX-"):]
                item.setText(self.NAME_COLUMN, bundle_name)
            item.setFlags(flags)
            item.setToolTip(self.NAME_COLUMN, synopsis)
            item.setCheckState(self.NAME_COLUMN, Qt.Unchecked)
            # Current version column
            item.setText(self.CURRENT_VERSION_COLUMN, str(installed_version))
            item.setTextAlignment(self.CURRENT_VERSION_COLUMN, Qt.AlignCenter)
            # New version column
            b = QComboBox()
            b.addItems(str(v) for v in sorted(new_versions, reverse=True))
            # TODO: set background color to same as other columns
            self.updates.setItemWidget(item, self.NEW_VERSION_COLUMN, b)

            # Category column
            item.setText(self.CATEGORY_COLUMN, category)

        self.updates.sortItems(self.NAME_COLUMN, Qt.AscendingOrder)
        for column in range(self.NUM_COLUMNS):
            self.updates.resizeColumnToContents(column)

    def new_choice(self):
        self._fill_updates()

    def install(self):
        from PyQt5.QtCore import Qt
        toolshed = self.session.toolshed
        logger = self.session.logger
        all_items = self.all_items
        updating = []
        for i in range(all_items.childCount()):
            item = all_items.child(i)
            if item.checkState(self.NAME_COLUMN) == Qt.Unchecked:
                continue
            bundle_name = item.data(self.NAME_COLUMN, Qt.UserRole)
            w = self.updates.itemWidget(item, self.NEW_VERSION_COLUMN)
            version = w.currentText()
            bi = toolshed.find_bundle(bundle_name,
                                      logger,
                                      version=version,
                                      installed=False)
            updating.append(bi)
        from . import _install_bundle
        _install_bundle(toolshed,
                        updating,
                        logger,
                        reinstall=True,
                        session=self.session)
        self.cancel()
Exemplo n.º 22
0
class LibAdd(ToolInstance):

    help = "https://github.com/QChASM/SEQCROW/wiki/Add-to-Personal-Library-Tool"
    SESSION_ENDURING = False
    SESSION_SAVE = False

    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 _build_ui(self):
        layout = QGridLayout()

        library_tabs = QTabWidget()

        #ligand tab
        ligand_tab = QWidget()
        ligand_layout = QFormLayout(ligand_tab)

        self.ligand_name = QLineEdit()
        self.ligand_name.setText("")
        self.ligand_name.setPlaceholderText("leave blank to preview")
        self.ligand_name.setToolTip(
            "name of ligand you are adding to your ligand library\nleave blank to open a new model with just the ligand"
        )
        ligand_layout.addRow("ligand name:", self.ligand_name)

        ligand_key_atoms = QPushButton("set key atoms to current selection")
        ligand_key_atoms.clicked.connect(self.update_key_atoms)
        ligand_key_atoms.setToolTip(
            "the current selection will be the key atoms for the ligand\nleave blank to automatically determine key atoms"
        )
        ligand_layout.addRow(ligand_key_atoms)

        libadd_ligand = QPushButton("add current selection to library")
        libadd_ligand.clicked.connect(self.libadd_ligand)
        ligand_layout.addRow(libadd_ligand)

        #substituent tab
        sub_tab = QWidget()
        sub_layout = QFormLayout(sub_tab)

        self.sub_name = QLineEdit()
        self.sub_name.setText("")
        self.sub_name.setPlaceholderText("leave blank to preview")
        self.sub_name.setToolTip(
            "name of substituent you are adding to your substituent library\nleave blank to open a new model with just the substituent"
        )
        sub_layout.addRow("substituent name:", self.sub_name)

        self.sub_confs = QSpinBox()
        self.sub_confs.setMinimum(1)
        sub_layout.addRow("number of conformers:", self.sub_confs)

        self.sub_angle = QSpinBox()
        self.sub_angle.setRange(0, 180)
        self.sub_angle.setSingleStep(30)
        sub_layout.addRow("angle between conformers:", self.sub_angle)

        libadd_sub = QPushButton("add current selection to library")
        libadd_sub.clicked.connect(self.libadd_substituent)
        sub_layout.addRow(libadd_sub)

        #ring tab
        ring_tab = QWidget()
        ring_layout = QFormLayout(ring_tab)

        self.ring_name = QLineEdit()
        self.ring_name.setText("")
        self.ring_name.setPlaceholderText("leave blank to preview")
        self.ring_name.setToolTip(
            "name of ring you are adding to your ring library\nleave blank to open a new model with just the ring"
        )
        ring_layout.addRow("ring name:", self.ring_name)

        libadd_ring = QPushButton("add ring with selected walk to library")
        libadd_ring.clicked.connect(self.libadd_ring)
        ring_layout.addRow(libadd_ring)

        library_tabs.addTab(sub_tab, "substituent")
        library_tabs.addTab(ring_tab, "ring")
        library_tabs.addTab(ligand_tab, "ligand")
        self.library_tabs = library_tabs

        layout.addWidget(library_tabs)

        whats_this = QLabel()
        whats_this.setText(
            "<a href=\"req\" style=\"text-decoration: none;\">what's this?</a>"
        )
        whats_this.setTextFormat(Qt.RichText)
        whats_this.setTextInteractionFlags(Qt.TextBrowserInteraction)
        whats_this.linkActivated.connect(self.open_link)
        whats_this.setToolTip(
            "click for more information about AaronTools libraries")
        layout.addWidget(whats_this)

        self.tool_window.ui_area.setLayout(layout)

        self.tool_window.manage(None)

    def update_key_atoms(self):
        selection = selected_atoms(self.session)
        if not selection.single_structure:
            raise RuntimeError("selected atoms must be on the same model")

        else:
            self.key_atomspec = selection

        self.tool_window.status("key atoms set to %s" %
                                " ".join(atom.atomspec for atom in selection))

    def libadd_ligand(self):
        """add ligand to library or open it in a new model"""
        selection = selected_atoms(self.session)

        if not selection.single_structure:
            raise RuntimeError("selected atoms must be on the same model")

        rescol = ResidueCollection(selection[0].structure)
        ligand_atoms = [
            atom for atom in rescol.atoms if atom.chix_atom in selection
        ]

        key_chix_atoms = [
            atom for atom in self.key_atomspec if not atom.deleted
        ]
        if len(key_chix_atoms) < 1:
            key_atoms = set([])
            for atom in ligand_atoms:
                for atom2 in atom.connected:
                    if atom2 not in ligand_atoms:
                        key_atoms.add(atom)

        else:
            key_atoms = rescol.find(
                [AtomSpec(atom.atomspec) for atom in key_chix_atoms])

        if len(key_atoms) < 1:
            raise RuntimeError("no key atoms could be determined")

        lig_name = self.ligand_name.text()
        ligand = Component(ligand_atoms, name=lig_name, key_atoms=key_atoms)
        ligand.comment = "K:%s" % ",".join(
            [str(ligand.atoms.index(atom) + 1) for atom in key_atoms])

        if len(lig_name) == 0:
            chimerax_ligand = ResidueCollection(ligand).get_chimera(
                self.session)
            chimerax_ligand.name = "ligand preview"
            self.session.models.add([chimerax_ligand])
            bild_obj = key_atom_highlight(ligand, [0.2, 0.5, 0.8, 0.5],
                                          self.session)
            self.session.models.add(bild_obj, parent=chimerax_ligand)

        else:
            check_aaronlib_dir()
            filename = os.path.join(AARONLIB, "Ligands", lig_name + ".xyz")
            if os.path.exists(filename):
                exists_warning = QMessageBox()
                exists_warning.setIcon(QMessageBox.Warning)
                exists_warning.setText(
                    "%s already exists.\nWould you like to overwrite?" %
                    filename)
                exists_warning.setStandardButtons(QMessageBox.Yes
                                                  | QMessageBox.No)

                rv = exists_warning.exec_()
                if rv == QMessageBox.Yes:
                    ligand.write(outfile=filename)
                    self.tool_window.status("%s added to ligand library" %
                                            lig_name)

                else:
                    self.tool_window.status(
                        "%s has not been added to ligand library" % lig_name)

            else:
                ligand.write(outfile=filename)
                self.tool_window.status("%s added to ligand library" %
                                        lig_name)

    def libadd_ring(self):
        """add ring to library or open it in a new model"""
        selection = self.session.seqcrow_ordered_selection_manager.selection

        if not selection.single_structure:
            raise RuntimeError("selected atoms must be on the same model")

        rescol = ResidueCollection(selection[0].structure)
        walk_atoms = rescol.find(
            [AtomSpec(atom.atomspec) for atom in selection])

        if len(walk_atoms) < 1:
            raise RuntimeError("no walk direction could be determined")

        ring_name = self.ring_name.text()
        ring = Ring(rescol, name=ring_name, end=walk_atoms)
        ring.comment = "E:%s" % ",".join(
            [str(rescol.atoms.index(atom) + 1) for atom in walk_atoms])

        if len(ring_name) == 0:
            chimerax_ring = ResidueCollection(ring).get_chimera(self.session)
            chimerax_ring.name = "ring preview"
            self.session.models.add([chimerax_ring])
            bild_obj = show_walk_highlight(ring, chimerax_ring,
                                           [0.9, 0.4, 0.3, 0.9], self.session)
            self.session.models.add(bild_obj, parent=chimerax_ring)

        else:
            check_aaronlib_dir()
            filename = os.path.join(AARONLIB, "Rings", ring_name + ".xyz")
            if os.path.exists(filename):
                exists_warning = QMessageBox()
                exists_warning.setIcon(QMessageBox.Warning)
                exists_warning.setText(
                    "%s already exists.\nWould you like to overwrite?" %
                    filename)
                exists_warning.setStandardButtons(QMessageBox.Yes
                                                  | QMessageBox.No)

                rv = exists_warning.exec_()
                if rv == QMessageBox.Yes:
                    ring.write(outfile=filename)
                    self.tool_window.status("%s added to ring library" %
                                            ring_name)

                else:
                    self.tool_window.status(
                        "%s has not been added to ring library" % ring_name)

            else:
                ring.write(outfile=filename)
                self.tool_window.status("%s added to ring library" % ring_name)

    def libadd_substituent(self):
        """add ligand to library or open it in a new model"""
        selection = selected_atoms(self.session)

        if not selection.single_structure:
            raise RuntimeError("selected atoms must be on the same model")

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

        rescol = ResidueCollection(selection[0].structure,
                                   convert_residues=residues)

        substituent_atoms = [
            atom for atom in rescol.atoms if atom.chix_atom in selection
        ]

        start = None
        avoid = None
        for atom in substituent_atoms:
            for atom2 in atom.connected:
                if atom2 not in substituent_atoms:
                    if start is None:
                        start = atom
                        avoid = atom2
                    else:
                        raise RuntimeError(
                            "substituent must only have one connection to the molecule"
                        )

        if start is None:
            raise RuntimeError(
                "substituent is not connected to a larger molecule")

        substituent_atoms.remove(start)
        substituent_atoms.insert(0, start)

        sub_name = self.sub_name.text()
        confs = self.sub_confs.value()
        angle = self.sub_angle.value()

        comment = "CF:%i,%i" % (confs, angle)
        if len(sub_name) == 0:
            sub = Substituent(substituent_atoms,
                              name="test",
                              conf_num=confs,
                              conf_angle=angle)
        else:
            sub = Substituent(substituent_atoms,
                              name=sub_name,
                              conf_num=confs,
                              conf_angle=angle)

        sub.comment = comment

        #align substituent bond to x axis
        sub.coord_shift(-avoid.coords)
        x_axis = np.array([1., 0., 0.])
        n = np.linalg.norm(start.coords)
        vb = start.coords / n
        d = np.linalg.norm(vb - x_axis)
        theta = np.arccos((d**2 - 2) / -2)
        vx = np.cross(vb, x_axis)
        sub.rotate(vx, theta)

        add = False
        if len(sub_name) == 0:
            chimerax_sub = ResidueCollection(sub).get_chimera(self.session)
            chimerax_sub.name = "substituent preview"
            self.session.models.add([chimerax_sub])
            bild_obj = ghost_connection_highlight(
                sub, [0.60784, 0.145098, 0.70196, 0.5], self.session)
            self.session.models.add(bild_obj, parent=chimerax_sub)

        else:
            check_aaronlib_dir()
            filename = os.path.join(AARONLIB, "Subs", sub_name + ".xyz")
            if os.path.exists(filename):
                exists_warning = QMessageBox()
                exists_warning.setIcon(QMessageBox.Warning)
                exists_warning.setText(
                    "%s already exists.\nWould you like to overwrite?" %
                    filename)
                exists_warning.setStandardButtons(QMessageBox.Yes
                                                  | QMessageBox.No)

                rv = exists_warning.exec_()
                if rv == QMessageBox.Yes:
                    add = True

                else:
                    self.tool_window.status(
                        "%s has not been added to substituent library" %
                        sub_name)

            else:
                add = True

        if add:
            sub.write(outfile=filename)
            self.tool_window.status("%s added to substituent library" %
                                    sub_name)
            register_selectors(self.session.logger, sub_name)
            if self.session.ui.is_gui:
                if (sub_name not in ELEMENTS and sub_name[0].isalpha()
                        and (len(sub_name) > 1
                             and not any(not (c.isalnum() or c in "+-")
                                         for c in sub_name[1:]))):
                    add_submenu = self.session.ui.main_window.add_select_submenu
                    add_selector = self.session.ui.main_window.add_menu_selector
                    substituent_menu = add_submenu(['Che&mistry'],
                                                   'Substituents')
                    add_selector(substituent_menu, sub_name, sub_name)

    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 open_link(self, *args):
        if self.library_tabs.currentIndex() == 0:
            link = "https://github.com/QChASM/AaronTools.py/wiki/AaronTools-Libraries#substituents"
        elif self.library_tabs.currentIndex() == 1:
            link = "https://github.com/QChASM/AaronTools.py/wiki/AaronTools-Libraries#rings"
        elif self.library_tabs.currentIndex() == 2:
            link = "https://github.com/QChASM/AaronTools.py/wiki/AaronTools-Libraries#ligands"

        run(self.session, "open %s" % link)
Exemplo n.º 23
0
    def __init__(self, session, tool_name):

        self._default_color = (0, 0, 204, 255)
        self._default_box_color = (0, 255, 0, 255)

        ToolInstance.__init__(self, session, tool_name)

        self.display_name = 'Measure and Color Blobs'

        from chimerax.ui import MainToolWindow
        tw = MainToolWindow(self)
        self.tool_window = tw
        parent = tw.ui_area

        from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QFrame, QCheckBox, QLabel, QPushButton, QSizePolicy
        from PyQt5.QtCore import Qt

        layout = QVBoxLayout(parent)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        parent.setLayout(layout)

        cf = QFrame(parent)
        #        cf.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)
        layout.addWidget(cf)

        clayout = QHBoxLayout(cf)
        clayout.setContentsMargins(0, 0, 0, 0)
        clayout.setSpacing(10)

        self._color_blob = cb = QCheckBox('Color blob', cf)
        cb.setCheckState(Qt.Checked)
        clayout.addWidget(cb)
        from chimerax.ui.widgets import ColorButton
        self._blob_color = cbut = ColorButton(cf, max_size=(16, 16))
        cbut.color = self._default_color
        clayout.addWidget(cbut)
        clayout.addSpacing(10)

        self._change_color = cc = QCheckBox('Change color automatically', cf)
        cc.setCheckState(Qt.Checked)
        clayout.addWidget(cc)
        clayout.addStretch(1)  # Extra space at end

        af = QFrame(parent)
        #        af.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)
        layout.addWidget(af)

        aflayout = QHBoxLayout(af)
        aflayout.setContentsMargins(0, 0, 0, 0)
        aflayout.setSpacing(10)

        self._show_box = bb = QCheckBox('Show principal axes box', af)
        bb.setCheckState(Qt.Checked)
        aflayout.addWidget(bb)
        self._box_color = bc = ColorButton(af, max_size=(16, 16))
        bc.color = self._default_box_color
        aflayout.addWidget(bc)
        aflayout.addSpacing(10)

        eb = QPushButton('Erase boxes', af)
        eb.clicked.connect(self._erase_boxes)
        aflayout.addWidget(eb)
        aflayout.addStretch(1)  # Extra space at end

        self._message_label = ml = QLabel(parent)
        layout.addWidget(ml)
        layout.addStretch(1)  # Extra space at end

        tw.manage(placement="side")
Exemplo n.º 24
0
class LigandSterimol(ToolInstance):

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

    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 _build_ui(self):
        layout = QFormLayout()

        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.L_option = QComboBox()
        self.L_option.addItems([
            "to centroid of coordinating atoms",
            "bisect angle between coordinating atoms"
        ])
        ndx = self.L_option.findText(self.settings.L_option, Qt.MatchExactly)
        self.L_option.setCurrentIndex(ndx)
        layout.addRow("L axis:", self.L_option)

        self.sterimol2vec = QGroupBox("Sterimol2Vec")
        sterimol2vec_layout = QFormLayout(self.sterimol2vec)

        self.at_L = QDoubleSpinBox()
        self.at_L.setRange(-10, 30)
        self.at_L.setDecimals(2)
        self.at_L.setSingleStep(0.25)
        self.at_L.setValue(self.settings.at_L)
        sterimol2vec_layout.addRow("L value:", self.at_L)

        layout.addRow(self.sterimol2vec)

        self.sterimol2vec.setCheckable(True)
        self.sterimol2vec.toggled.connect(lambda x: self.at_L.setEnabled(x))
        self.sterimol2vec.setChecked(self.settings.sterimol2vec)

        self.display_vectors = QCheckBox()
        self.display_vectors.setChecked(self.settings.display_vectors)
        layout.addRow("show vectors:", self.display_vectors)

        self.display_radii = QCheckBox()
        self.display_radii.setChecked(self.settings.display_radii)
        layout.addRow("show radii:", self.display_radii)

        calc_sterimol_button = QPushButton(
            "calculate parameters for selected ligands")
        calc_sterimol_button.clicked.connect(self.calc_sterimol)
        layout.addRow(calc_sterimol_button)
        self.calc_sterimol_button = calc_sterimol_button

        remove_sterimol_button = QPushButton("remove Sterimol visualizations")
        remove_sterimol_button.clicked.connect(self.del_sterimol)
        layout.addRow(remove_sterimol_button)
        self.remove_sterimol_button = remove_sterimol_button

        self.table = QTableWidget()
        self.table.setColumnCount(8)
        self.table.setHorizontalHeaderLabels([
            'model',
            'coord. atoms',
            'B\u2081',
            'B\u2082',
            'B\u2083',
            'B\u2084',
            'B\u2085',
            'L',
        ])
        self.table.setSelectionBehavior(QTableWidget.SelectRows)
        self.table.setEditTriggers(QTableWidget.NoEditTriggers)
        self.table.resizeColumnToContents(0)
        self.table.resizeColumnToContents(1)
        self.table.resizeColumnToContents(2)
        self.table.resizeColumnToContents(3)
        self.table.resizeColumnToContents(4)
        self.table.resizeColumnToContents(5)
        self.table.resizeColumnToContents(6)
        self.table.resizeColumnToContents(7)
        self.table.horizontalHeader().setSectionResizeMode(
            2, QHeaderView.Stretch)
        self.table.horizontalHeader().setSectionResizeMode(
            3, QHeaderView.Stretch)
        self.table.horizontalHeader().setSectionResizeMode(
            4, QHeaderView.Stretch)
        self.table.horizontalHeader().setSectionResizeMode(
            5, QHeaderView.Stretch)
        self.table.horizontalHeader().setSectionResizeMode(
            6, QHeaderView.Stretch)
        self.table.horizontalHeader().setSectionResizeMode(
            7, 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 calc_sterimol(self, *args):
        self.settings.radii = self.radii_option.currentText()
        self.settings.display_radii = self.display_radii.checkState(
        ) == Qt.Checked
        self.settings.display_vectors = self.display_vectors.checkState(
        ) == Qt.Checked
        self.settings.at_L = self.at_L.value()
        self.settings.sterimol2vec = self.sterimol2vec.isChecked()
        self.settings.L_option = self.L_option.currentText()

        targets, neighbors, datas = sterimol_cmd(
            self.session,
            selected_atoms(self.session),
            radii=self.radii_option.currentText(),
            showVectors=self.display_vectors.checkState() == Qt.Checked,
            showRadii=self.display_radii.checkState() == Qt.Checked,
            return_values=True,
            at_L=self.at_L.value() if self.sterimol2vec.isChecked() else None,
            bisect_L=self.L_option.currentText() ==
            "bisect angle between coordinating atoms",
        )

        if len(targets) == 0:
            return

        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 = ";"

        # self.table.setRowCount(0)

        for t, b, data in zip(targets, neighbors, datas):
            row = self.table.rowCount()
            self.table.insertRow(row)

            targ = QTableWidgetItem()
            targ.setData(Qt.DisplayRole, t)
            self.table.setItem(row, 0, targ)

            neigh = QTableWidgetItem()
            neigh.setData(Qt.DisplayRole, delim.join(b))
            self.table.setItem(row, 1, neigh)

            l = np.linalg.norm(data["L"][1] - data["L"][0])
            b1 = np.linalg.norm(data["B1"][1] - data["B1"][0])
            b2 = np.linalg.norm(data["B2"][1] - data["B2"][0])
            b3 = np.linalg.norm(data["B3"][1] - data["B3"][0])
            b4 = np.linalg.norm(data["B4"][1] - data["B4"][0])
            b5 = np.linalg.norm(data["B5"][1] - data["B5"][0])

            li = QTableWidgetItem()
            li.setData(Qt.DisplayRole, "%.2f" % l)
            self.table.setItem(row, 7, li)

            b1i = QTableWidgetItem()
            b1i.setData(Qt.DisplayRole, "%.2f" % b1)
            self.table.setItem(row, 2, b1i)

            b2i = QTableWidgetItem()
            b2i.setData(Qt.DisplayRole, "%.2f" % b2)
            self.table.setItem(row, 3, b2i)

            b3i = QTableWidgetItem()
            b3i.setData(Qt.DisplayRole, "%.2f" % b3)
            self.table.setItem(row, 4, b3i)

            b4i = QTableWidgetItem()
            b4i.setData(Qt.DisplayRole, "%.2f" % b4)
            self.table.setItem(row, 5, b4i)

            b5i = QTableWidgetItem()
            b5i.setData(Qt.DisplayRole, "%.2f" % b5)
            self.table.setItem(row, 6, b5i)

        for i in range(0, 7):
            if i == 1:
                continue
            self.table.resizeColumnToContents(i)

    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([
                "substituent_atom", "bonded_atom", "B1", "B2", "B3", "B4",
                "B5", "L"
            ])
            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, 8)]
            ])
            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_sterimol(self):
        for model in self.session.models.list(type=Generic3DModel):
            if model.name.startswith("Sterimol"):
                model.delete()

    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 "")
Exemplo n.º 25
0
class HelpUI(ToolInstance):

    # do not close when opening session (especially if web page asked to open session)
    SESSION_ENDURING = True

    help = "help:user/tools/helpviewer.html"

    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)

    def status(self, message):
        self.status_bar.showMessage(message, 2000)

    def create_tab(self, *, empty=False, background=False):
        w = _HelpWebView(self.session, self, profile=self.profile)
        self.tabs.addTab(w, "New Tab")
        if empty:
            from chimerax import app_dirs
            self.tool_window.title = app_dirs.appname
            from PyQt5.QtCore import Qt
            self.url.setFocus(Qt.ShortcutFocusReason)
        if not background:
            self.tabs.setCurrentWidget(w)
        p = w.page()
        p.loadFinished.connect(lambda okay, w=w: self.page_loaded(w, okay))
        p.urlChanged.connect(lambda url, w=w: self.url_changed(w, url))
        p.titleChanged.connect(lambda title, w=w: self.title_changed(w, title))
        p.linkHovered.connect(self.link_hovered)
        p.authenticationRequired.connect(self.authorize)
        # TODO? p.iconChanged.connect(....)
        # TODO? p.iconUrlChanged.connect(....)
        # TODO? p.loadProgress.connect(....)
        # TODO? p.loadStarted.connect(....)
        # TODO? p.renderProcessTerminated.connect(....)
        # TODO? p.selectionChanged.connect(....)
        # TODO? p.windowCloseRequested.connect(....)
        return w

    def authorize(self, requestUrl, auth):
        from PyQt5.QtWidgets import QDialog, QGridLayout, QLineEdit, QLabel, QPushButton
        from PyQt5.QtCore import Qt

        class PasswordDialog(QDialog):
            def __init__(self, requestUrl, auth, parent=None):
                super().__init__(parent)
                self.setWindowTitle("ChimeraX: Authentication Required")
                self.setModal(True)
                self.auth = auth
                url = requestUrl.url()
                key = QLabel("\N{KEY}")
                font = key.font()
                font.setPointSize(2 * font.pointSize())
                key.setFont(font)
                self.info = QLabel(
                    f'{url} is requesting your username and password.  The site says: "{auth.realm()}"'
                )
                self.info.setWordWrap(True)
                user_name = QLabel("User name:")
                self.user_name = QLineEdit(self)
                password = QLabel("Password:"******"item" is an instance of QWebEngineDownloadItem
        # print("HelpUI.download_requested", item)
        import os
        url_file = item.url().fileName()
        base, extension = os.path.splitext(url_file)
        # print("HelpUI.download_requested connect", item.mimeType(), extension)
        # Normally, we would look at the download type or MIME type,
        # but since neither one is set by the server, we look at the
        # download extension instead
        if extension == ".whl":
            if not base.endswith("x86_64"):
                # Since the file name encodes the package name and version
                # number, we make sure that we are using the right name
                # instead of whatever QWebEngine may want to use.
                # Remove _# which may be present if bundle author submitted
                # the same version of the bundle multiple times.
                parts = base.rsplit('_', 1)
                if len(parts) == 2 and parts[1].isdigit():
                    url_file = parts[0] + extension
            file_path = os.path.join(os.path.dirname(item.path()), url_file)
            import pkg_resources
            py_env = pkg_resources.Environment()
            dist = pkg_resources.Distribution.from_filename(file_path)
            if not py_env.can_add(dist):
                raise ValueError("unsupported wheel platform")
            item.setPath(file_path)
            # print("HelpUI.download_requested clean", file_path)
            try:
                # Guarantee that file name is available
                os.remove(file_path)
            except OSError:
                pass
            self._pending_downloads.append(item)
            self.session.logger.info("Downloading bundle %s" % url_file)
            item.finished.connect(self.download_finished)
        else:
            from PyQt5.QtWidgets import QFileDialog
            path, filt = QFileDialog.getSaveFileName(directory=item.path())
            if not path:
                return
            self.session.logger.info("Downloading file %s" % url_file)
            item.setPath(path)
        # print("HelpUI.download_requested accept", file_path)
        item.accept()

    def download_finished(self, *args, **kw):
        # print("HelpUI.download_finished", args, kw)
        finished = []
        pending = []
        for item in self._pending_downloads:
            if not item.isFinished():
                pending.append(item)
            else:
                finished.append(item)
        self._pending_downloads = pending
        import pkginfo
        from chimerax.ui.ask import ask
        for item in finished:
            item.finished.disconnect()
            filename = item.path()
            try:
                w = pkginfo.Wheel(filename)
            except Exception as e:
                self.session.logger.info("Error parsing %s: %s" %
                                         (filename, str(e)))
                self.session.logger.info("File saved as %s" % filename)
                continue
            if not _installable(w, self.session.logger):
                self.session.logger.info("Bundle saved as %s" % filename)
                continue
            how = ask(self.session,
                      "Install %s %s (file %s)?" %
                      (w.name, w.version, filename), ["install", "cancel"],
                      title="Toolshed")
            if how == "cancel":
                self.session.logger.info("Bundle installation canceled")
                continue
            self.session.toolshed.install_bundle(filename,
                                                 self.session.logger,
                                                 per_user=True,
                                                 session=self.session)

    def show(self, url, *, new_tab=False, html=None):
        from urllib.parse import urlparse, urlunparse
        parts = urlparse(url)
        if not parts.scheme:
            parts = list(parts)
            parts[0] = "http"
        url = urlunparse(parts)  # canonicalize
        if new_tab or self.tabs.count() == 0:
            w = self.create_tab()
        else:
            w = self.tabs.currentWidget()
        from PyQt5.QtCore import QUrl
        if html:
            w.setHtml(html, QUrl(url))
        else:
            w.setUrl(QUrl(url))
        self.display(True)

    def go_to(self):
        self.show(self.url.text())

    def page_back(self, checked):
        w = self.tabs.currentWidget()
        if w is None:
            return
        w.history().back()

    def page_forward(self, checked):
        w = self.tabs.currentWidget()
        if w is None:
            return
        w.history().forward()

    def page_home(self, checked):
        w = self.tabs.currentWidget()
        if w is None:
            return
        history = w.history()
        hi = history.itemAt(0)
        self.show(_qurl2text(hi.url()))

    def page_zoom_in(self):
        w = self.tabs.currentWidget()
        if w is None:
            return
        w.setZoomFactor(1.25 * w.zoomFactor())

    def page_zoom_out(self):
        w = self.tabs.currentWidget()
        if w is None:
            return
        w.setZoomFactor(0.8 * w.zoomFactor())

    def page_reset_zoom(self):
        w = self.tabs.currentWidget()
        if w is None:
            return
        w.setZoomFactor(1)

    def page_reload(self, checked):
        w = self.tabs.currentWidget()
        if w is None:
            return
        w.reload()

    def page_search(self):
        w = self.tabs.currentWidget()
        if w is None:
            return
        w.findText(self.search_terms.text())

    def delete(self):
        global _singleton
        _singleton = None
        ToolInstance.delete(self)

    def page_loaded(self, w, okay):
        if self.tabs.currentWidget() != w:
            return
        self.update_back_forward(w)

    def url_changed(self, w, url):
        if self.tabs.currentWidget() != w:
            return
        self.url.setText(_qurl2text(url))
        self.update_back_forward(w)

    def title_changed(self, w, title):
        if self.tabs.currentWidget() == w:
            self.tool_window.title = title
        i = self.tabs.indexOf(w)
        self.tabs.setTabText(i, title)

    def link_hovered(self, url):
        from PyQt5.QtCore import QUrl
        try:
            self.status(_qurl2text(QUrl(url)))
        except Exception:
            self.status(url)

    def tab_changed(self, i):
        if i >= 0:
            tab_text = self.tabs.tabText(i)
            if tab_text != "New Tab":
                self.tool_window.title = tab_text
                self.url.setText(_qurl2text(self.tabs.currentWidget().url()))
        else:
            # no more tabs
            self.display(False)
        self.update_back_forward()

    def close_tab(self, i):
        w = self.tabs.widget(i)
        self.tabs.removeTab(i)
        w.deleteLater()
        self.update_back_forward()

    def close_current_tab(self):
        i = self.tabs.currentIndex()
        if i != -1:
            self.close_tab(i)

    def cycle_tab(self, incr):
        i = self.tabs.currentIndex()
        if i == -1:
            return
        count = self.tabs.count()
        i = (i + incr) % count
        self.tabs.setCurrentIndex(i)

    def tab_n(self, n):
        count = self.tabs.count()
        if count == 0:
            return
        if n == -1:
            self.tabs.setCurrentIndex(count - 1)
        elif n < count:
            self.tabs.setCurrentIndex(n)

    def update_back_forward(self, w=None):
        if w is None:
            w = self.tabs.currentWidget()
        history = w.history()
        self.back.setEnabled(history.canGoBack())
        self.forward.setEnabled(history.canGoForward())

    @classmethod
    def get_viewer(cls, session, target=None):
        global _singleton
        if _singleton is None:
            _singleton = HelpUI(session)
        return _singleton
Exemplo n.º 26
0
    def __init__(self, session, tool_name):

        self._default_color = (255, 153, 204, 128)  # Transparent pink
        self._max_slider_value = 1000  # QSlider only handles integer values
        self._max_slider_radius = 100.0  # Float maximum radius value, scene units
        self._block_text_update = False  # Avoid radius slider and text continuous updating each other.
        self._block_slider_update = False  # Avoid radius slider and text continuous updating each other.

        b = session.main_view.drawing_bounds()
        vradius = 100 if b is None else b.radius()
        self._max_slider_radius = vradius
        center = b.center() if b else (0, 0, 0)
        self._sphere_model = SphereModel('eraser sphere', session,
                                         self._default_color, center,
                                         0.2 * vradius)

        ToolInstance.__init__(self, session, tool_name)

        self.display_name = 'Map Eraser'

        from chimerax.ui import MainToolWindow
        tw = MainToolWindow(self)
        self.tool_window = tw
        parent = tw.ui_area

        from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QFrame, QCheckBox, QLabel, QPushButton, QLineEdit, QSlider
        from PyQt5.QtCore import Qt

        layout = QVBoxLayout(parent)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        parent.setLayout(layout)

        sf = QFrame(parent)
        layout.addWidget(sf)

        slayout = QHBoxLayout(sf)
        slayout.setContentsMargins(0, 0, 0, 0)
        slayout.setSpacing(10)

        self._show_eraser = se = QCheckBox('Show map eraser sphere', sf)
        se.setCheckState(Qt.Checked)
        se.stateChanged.connect(self._show_eraser_cb)
        slayout.addWidget(se)
        from chimerax.ui.widgets import ColorButton
        self._sphere_color = sc = ColorButton(sf,
                                              max_size=(16, 16),
                                              has_alpha_channel=True)
        sc.color = self._default_color
        sc.color_changed.connect(self._change_color_cb)
        slayout.addWidget(sc)
        slayout.addStretch(1)  # Extra space at end

        rf = QFrame(parent)
        layout.addWidget(rf)
        rlayout = QHBoxLayout(rf)
        rlayout.setContentsMargins(0, 0, 0, 0)
        rlayout.setSpacing(4)

        rl = QLabel('Radius', rf)
        rlayout.addWidget(rl)
        self._radius_entry = rv = QLineEdit('', rf)
        rv.setMaximumWidth(40)
        rv.returnPressed.connect(self._radius_changed_cb)
        rlayout.addWidget(rv)
        self._radius_slider = rs = QSlider(Qt.Horizontal, rf)
        smax = self._max_slider_value
        rs.setRange(0, smax)
        rs.valueChanged.connect(self._radius_slider_moved_cb)
        rlayout.addWidget(rs)

        rv.setText('%.4g' % self._sphere_model.radius)
        self._radius_changed_cb()

        ef = QFrame(parent)
        layout.addWidget(ef)
        elayout = QHBoxLayout(ef)
        elayout.setContentsMargins(0, 0, 0, 0)
        elayout.setSpacing(30)
        eb = QPushButton('Erase inside sphere', ef)
        eb.clicked.connect(self._erase_in_sphere)
        elayout.addWidget(eb)
        eo = QPushButton('Erase outside sphere', ef)
        eo.clicked.connect(self._erase_outside_sphere)
        elayout.addWidget(eo)
        rb = QPushButton('Reduce map bounds', ef)
        rb.clicked.connect(self._crop_map)
        elayout.addWidget(rb)
        elayout.addStretch(1)  # Extra space at end

        layout.addStretch(1)  # Extra space at end

        tw.manage(placement="side")

        # When displayed models change update radius slider range.
        from chimerax.core.models import MODEL_DISPLAY_CHANGED
        h = session.triggers.add_handler(MODEL_DISPLAY_CHANGED,
                                         self._model_display_change)
        self._model_display_change_handler = h
Exemplo n.º 27
0
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 "")
Exemplo n.º 28
0
class TutorialTool(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 = "Tutorial — Qt-based"

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

    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 PyQt5.QtWidgets import QLabel, QLineEdit, QHBoxLayout
        layout = QHBoxLayout()
        layout.addWidget(QLabel("Log this text:"))
        self.line_edit = QLineEdit()

        # Arrange for our 'return_pressed' method to be called when the
        # user presses the Return key
        self.line_edit.returnPressed.connect(self.return_pressed)
        layout.addWidget(self.line_edit)

        # Set the layout as the contents of our window
        self.tool_window.ui_area.setLayout(layout)

        # Show the window on the user-preferred side of the ChimeraX
        # main window
        self.tool_window.manage('side')

    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
        clear_action = QAction("Clear", menu)
        clear_action.triggered.connect(lambda *args: self.line_edit.clear())
        menu.addAction(clear_action)

    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, "Tutorial (Qt)")
        inst.line_edit.setText(data['current text'])
        return inst
Exemplo n.º 29
0
Arquivo: tool.py Projeto: dials/cctbx
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
Exemplo n.º 30
0
class RMFViewer(ToolInstance):
    SESSION_ENDURING = False  # Does this instance persist when session closes
    SESSION_SAVE = True  # We do save/restore in sessions
    help = "help:user/tools/rmf.html"

    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 take_snapshot(self, session, flags):
        data = {
            'version': 1,
            'tool_name': self.tool_name,
            'selmodel': self.model_list.currentIndex()
        }
        return data

    @classmethod
    def restore_snapshot(cls, session, data):
        t = cls(session, data['tool_name'])
        t.model_list.setCurrentIndex(data['selmodel'])
        return t

    def _fill_ui(self, *args):
        self.rmf_models = [
            m for m in self.session.models.list()
            if hasattr(m, 'rmf_hierarchy')
        ]

        self.model_list.clear()
        self.model_list.addItems("%s; #%s" % (m.name, m.id_string)
                                 for m in self.rmf_models)

        self._fill_model_stack()

    def model_list_change(self, i):
        self.model_stack.setCurrentIndex(i)

    def _build_ui(self):
        layout = QtWidgets.QVBoxLayout()
        self.tool_window.ui_area.setLayout(layout)
        self.tool_window.manage('side')

        combo_and_label = QtWidgets.QHBoxLayout()
        label = QtWidgets.QLabel("RMF model")
        combo_and_label.addWidget(label)
        self.model_list = QtWidgets.QComboBox()
        self.model_list.currentIndexChanged.connect(self.model_list_change)
        combo_and_label.addWidget(self.model_list, stretch=4)
        layout.addLayout(combo_and_label)

        self.model_stack = QtWidgets.QStackedWidget()
        layout.addWidget(self.model_stack)

    def _fill_model_stack(self):
        self.model_stack.blockSignals(True)
        for i in range(self.model_stack.count()):
            self.model_stack.removeWidget(self.model_stack.widget(0))
        for m in self.rmf_models:
            self.model_stack.addWidget(self._build_ui_rmf_model(m))
        self.model_stack.blockSignals(False)

    def _build_ui_rmf_model(self, m):
        top = QtWidgets.QSplitter(Qt.Vertical)

        pane = QtWidgets.QWidget()
        layout = QtWidgets.QVBoxLayout()
        pane.setLayout(layout)

        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        label = QtWidgets.QLabel("Features")
        layout.addWidget(label)

        tree = QtWidgets.QTreeView()
        tree.setAnimated(False)
        tree.setIndentation(20)
        tree.setSelectionMode(QtWidgets.QTreeView.ExtendedSelection)
        tree.setSortingEnabled(False)
        tree.setHeaderHidden(True)
        tree.setModel(_RMFFeaturesModel(m.rmf_features))
        tree.selectionModel().selectionChanged.connect(
            lambda sel, desel, tree=tree: self._select_feature(tree))
        layout.addWidget(tree)
        top.addWidget(pane)

        pane, hierarchy_tree = self._get_hierarchy_pane(m)
        top.addWidget(pane)

        pane = self._get_provenance_pane(m, hierarchy_tree)
        top.addWidget(pane)

        return top

    def _get_hierarchy_pane(self, m):
        pane = QtWidgets.QWidget()
        layout = QtWidgets.QVBoxLayout()
        pane.setLayout(layout)

        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)

        label_and_res = QtWidgets.QHBoxLayout()
        label_and_res.setContentsMargins(0, 0, 0, 0)
        label_and_res.setSpacing(0)

        label = QtWidgets.QLabel("Hierarchy @")
        label_and_res.addWidget(label)

        tree = QtWidgets.QTreeView()

        for res in sorted(m._rmf_resolutions):
            cb = QtWidgets.QCheckBox("%.1f" % res)
            cb.setChecked(res in m._selected_rmf_resolutions)
            cb.clicked.connect(
                lambda *, cb=cb, tree=tree, resolution=res: self.
                _resolution_button_clicked(cb, tree, resolution))
            label_and_res.addWidget(cb)

        layout.addLayout(label_and_res)

        tree.setAnimated(False)
        tree.setIndentation(20)
        tree.setSelectionMode(QtWidgets.QTreeView.ExtendedSelection)
        tree.setSortingEnabled(False)
        tree.setHeaderHidden(True)
        tree.setModel(
            _RMFHierarchyModel(m.rmf_hierarchy, m._selected_rmf_resolutions))

        tree_and_buttons = QtWidgets.QHBoxLayout()
        tree_and_buttons.setContentsMargins(0, 0, 0, 0)
        tree_and_buttons.setSpacing(0)

        tree_and_buttons.addWidget(tree, stretch=1)

        buttons = QtWidgets.QVBoxLayout()
        buttons.setContentsMargins(0, 0, 0, 0)
        buttons.setSpacing(0)
        select_button = QtWidgets.QPushButton("Select")
        # In Qt one-argument callbacks get called with a bool argument;
        # throw this away
        select_button.clicked.connect(
            lambda *, tree=tree: self._select_button_clicked(tree))
        buttons.addWidget(select_button)
        hide_button = QtWidgets.QPushButton("Hide")
        hide_button.clicked.connect(
            lambda *, tree=tree: self._hide_button_clicked(tree))
        buttons.addWidget(hide_button)
        show_button = QtWidgets.QPushButton("Show")
        show_button.clicked.connect(
            lambda *, tree=tree: self._show_button_clicked(tree))
        buttons.addWidget(show_button)
        show_only_button = QtWidgets.QPushButton("Only")
        show_only_button.clicked.connect(
            lambda *, tree=tree: self._show_only_button_clicked(tree))
        buttons.addWidget(show_only_button)
        view_button = QtWidgets.QPushButton("View")
        view_button.clicked.connect(
            lambda *, tree=tree: self._view_button_clicked(tree))
        buttons.addWidget(view_button)
        tree_and_buttons.addLayout(buttons)
        layout.addLayout(tree_and_buttons, stretch=1)
        return pane, tree

    def _get_provenance_pane(self, m, hierarchy_tree):
        pane = QtWidgets.QWidget()
        layout = QtWidgets.QVBoxLayout()
        pane.setLayout(layout)

        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        label = QtWidgets.QLabel("Provenance")
        layout.addWidget(label)

        tree = QtWidgets.QTreeView()
        tree.setAnimated(False)
        tree.setIndentation(20)
        tree.setSelectionMode(QtWidgets.QTreeView.ExtendedSelection)
        tree.setSortingEnabled(False)
        tree.setHeaderHidden(True)
        tree.setModel(_RMFProvenanceModel(m.rmf_provenance))
        tree.selectionModel().selectionChanged.connect(
            lambda sel, desel, tree=tree, hierarchy_tree=hierarchy_tree: self.
            _select_provenance(tree, hierarchy_tree))

        tree_and_buttons = QtWidgets.QHBoxLayout()
        tree_and_buttons.setContentsMargins(0, 0, 0, 0)
        tree_and_buttons.setSpacing(0)

        tree_and_buttons.addWidget(tree, stretch=1)

        buttons = QtWidgets.QVBoxLayout()
        buttons.setContentsMargins(0, 0, 0, 0)
        buttons.setSpacing(0)
        load_button = QtWidgets.QPushButton("Load")
        load_button.clicked.connect(
            lambda *, tree=tree, m=m: self._load_button_clicked(tree, m))
        buttons.addWidget(load_button)
        tree_and_buttons.addLayout(buttons)
        layout.addLayout(tree_and_buttons, stretch=1)
        return pane

    def _get_selected_chimera_objects(self, tree):
        def _get_node_objects(node, objs):
            o = node.chimera_obj
            if o and not o.deleted:
                objs.append(o)
            for child in node._filtered_children:
                _get_node_objects(child, objs)

        objs = []
        inds = tree.selectedIndexes()
        for ind in inds:
            _get_node_objects(ind.internalPointer(), objs)
        # If empty selection, use the root instead
        if not inds:
            _get_node_objects(tree.model().rmf_hierarchy, objs)
        objects = Objects()
        objects.add_atoms(Atoms(x for x in objs if isinstance(x, Atom)))
        objects.add_bonds(Bonds(x for x in objs if isinstance(x, Bond)))
        return objects

    def _get_selected_features(self, tree):
        def get_child_chimera_obj(feat):
            for child in feat.children:
                o = child.chimera_obj
                if o:
                    yield o
                for obj in get_child_chimera_obj(child):
                    yield obj

        def get_selection():
            for f in tree.selectedIndexes():
                feat = f.internalPointer()
                obj = feat.chimera_obj
                # Prefer to select pseudobonds (even from children)
                if (obj is not None
                        and (not isinstance(obj, Atoms) or not feat.children)):
                    yield obj
                else:
                    for obj in get_child_chimera_obj(feat):
                        yield obj

        s = list(get_selection())
        objs = Objects()
        objs.add_pseudobonds(
            Pseudobonds(x for x in s if isinstance(x, Pseudobond)))
        for x in s:
            if isinstance(x, Atoms):
                objs.add_atoms(x)
        return objs

    def _select_button_clicked(self, tree):
        from chimerax.std_commands.select import select
        select(self.session, self._get_selected_chimera_objects(tree))

    def _show_button_clicked(self, tree):
        from chimerax.std_commands.show import show
        show(self.session, self._get_selected_chimera_objects(tree))

    def _hide_button_clicked(self, tree):
        from chimerax.std_commands.hide import hide
        hide(self.session, self._get_selected_chimera_objects(tree))

    def _view_button_clicked(self, tree):
        from chimerax.std_commands.view import view
        view(self.session, self._get_selected_chimera_objects(tree))

    def _show_only_button_clicked(self, tree):
        def show_only(node, show_roots, under_root=False, show=True):
            if not under_root and show and node in show_roots:
                under_root = True
            o = node.chimera_obj
            if o:
                o.display = under_root and show
            if under_root:
                to_show = frozenset(node._filtered_children)
                for child in node.children:
                    show_only(child, show_roots, under_root, child in to_show)
            else:
                for child in node.children:
                    show_only(child, show_roots, under_root)

        show_roots = frozenset(ind.internalPointer()
                               for ind in tree.selectedIndexes())
        top = tree.model().rmf_hierarchy
        if not show_roots:
            show_roots = frozenset([top])
        show_only(top, show_roots)

    def _select_feature(self, tree):
        from chimerax.std_commands.select import select
        select(self.session, self._get_selected_features(tree))

    def _select_provenance(self, tree, hierarchy_tree):
        mode = QItemSelectionModel.ClearAndSelect
        hierarchy_model = hierarchy_tree.model()
        hierarchy_selmodel = hierarchy_tree.selectionModel()
        for f in tree.selectedIndexes():
            obj = f.internalPointer()
            if obj.hierarchy_node:
                ind = hierarchy_model.index_for_node(obj.hierarchy_node)
                if ind.isValid():
                    hierarchy_selmodel.setCurrentIndex(ind, mode)
                mode = QItemSelectionModel.Select

    def _load_button_clicked(self, tree, m):
        m._update_provenance_map()
        objs = [f.internalPointer() for f in tree.selectedIndexes()]
        # If empty selection, load everything
        for obj in objs or tree.model().rmf_provenance:
            obj.load(self.session, m)

    def _resolution_button_clicked(self, checkbox, tree, resolution):
        model = tree.model()
        selmodel = tree.selectionModel()
        selected = [
            ind.internalPointer() for ind in selmodel.selectedIndexes()
        ]
        model.set_resolution_filter(resolution, checkbox.isChecked())
        mode = QItemSelectionModel.ClearAndSelect
        for s in selected:
            ind = model.index_for_node(s)
            if ind.isValid():
                selmodel.setCurrentIndex(ind, mode)
                mode = QItemSelectionModel.Select