Source code for schrodinger.ui.qt.navtoolbar

"""
Custom versions of the Matplotlib Qt Navigation toolbar

Copyright Schrodinger, LLC. All rights reserved.
"""

import os
import os.path

import matplotlib
from matplotlib.backend_bases import cursors
from matplotlib.backends.backend_qt5 import SubplotToolQt
from matplotlib.backends.backend_qt5agg import \
    NavigationToolbar2QT as NavToolbarQt

import schrodinger
from schrodinger.Qt import QtCore
from schrodinger.Qt import QtGui
from schrodinger.Qt import QtWidgets
from schrodinger.ui.qt import figureoptions
from schrodinger.ui.qt import icons

Qt = QtCore.Qt
maestro = schrodinger.get_maestro()

cursord = {
    cursors.MOVE: QtCore.Qt.SizeAllCursor,
    cursors.HAND: QtCore.Qt.PointingHandCursor,
    cursors.POINTER: QtCore.Qt.ArrowCursor,
    cursors.SELECT_REGION: QtCore.Qt.CrossCursor,
    cursors.WAIT: QtCore.Qt.WaitCursor,
}

CUSTOMIZE_TOOLITEM = ('Customize', 'Edit axis, curve and image parameters',
                      'qt4_editor_options', 'edit_parameters')

ACTION_NAMES = {
    'PAN': 'pan',
    'ZOOM': 'zoom',
}





[docs]class MaestroNavToolbar(NavToolbar): """ The toolbar for the Maestro Manage Plots panel :cvar DEFAULT_ICON_PREFIX: The default prefix to add to icon names. If the icon name starts with a colon, this prefix will not be used. :vartype DEFAULT_ICON_PREFIX: str """ DEFAULT_ICON_PREFIX = ":projplot_icons/"
[docs] def __init__(self, canvas, parent, projectplot, coordinates=True, show_label_button=False): """ Create the toolbar :param canvas: The matplotlib canvas :type canvas: `schrodinger.mpl_backend_agg.FigureCanvasQTAgg` :param parent: The Qt parent widget :type parent: `QtWidgets.QWidget` :param projectplot: The panel that this toolbar is part of :type projectplot: `projplot.ProjPlot` or `projectPlot.ProjectPlot` :param coordinates: Whether the cursor coordinates should be shown on the right side of the toolbar :type coordinates: bool :param show_label_button: Whether a label button should be included :type show_label_button: bool """ self.show_label_button = show_label_button self.controls_visible = True self.plot = projectplot super(MaestroNavToolbar, self).__init__(canvas, parent, coordinates, clipboard=False) self._setActionsCheckable()
def _populateToolitems(self, custom_toolitems, customize=True): # See parent class for docstring self.toolitems = [ ('Home', 'Reset original view', 'home_view', 'home'), ('Back', 'Back to previous view', 'previous_view', 'back'), ('Forward', 'Forward to next view', 'next_view', 'forward'), ('Pan', 'Pan axes with left mouse, zoom with right', 'pan', 'pan'), ('Zoom', 'Zoom to rectangle', 'zoom', 'zoom'), (None, None, None, None), ('Project Table', 'Open Project Table', 'show_pt', "showPT"), (None, None, None, None), ('Include', 'Pick to include entries', 'include', 'pickInclude'), ('Select', 'Pick to select entries', 'select', 'pickSelect') ] if self.show_label_button: self.toolitems.append( ('Label', 'Hover over datapoint to view ' 'property or click to add label', 'label', 'pickLabel')) self.toolitems.extend([ ('Subplots', 'Configure plot', 'configure', 'configure_subplots'), ('Save', 'Save the figure', 'save_image', 'save_figure'), (None, None, None, None), ('Hide Controls', 'Show or hide plot controls', 'toggleControls', 'toggleControls') ]) def _icon(self, name): # See parent class for docstring if name.startswith("toggleControls"): return QtGui.QIcon() elif name.startswith(":"): return QtGui.QIcon(name) else: return QtGui.QIcon(self.DEFAULT_ICON_PREFIX + name) def _setActionsCheckable(self): """ Make a subset of buttons checkable. Note that matplotlib sets "pan" and "zoom" as checkable already. """ checkable_actions = ["pickInclude", "pickSelect"] if self.show_label_button: checkable_actions.append("pickLabel") for action_name in checkable_actions: self._actions[action_name].setCheckable(True)
[docs] def toggleControls(self): """ Toggle the visibility of the plot controls. """ action = self._actions["toggleControls"] if self.controls_visible: self.controls_visible = False action.setIconText("Show Controls") else: self.controls_visible = True action.setIconText("Hide Controls") self.plot.controlCB()
def _toggleMode(self, name, mode, mode_func): """ Toggle the specified toolbar button on or off :param name: The name of the action (i.e. the name of the slot function this button calls) :type name: str :param mode: The text to put in the toolbar if activating the mode :type mode: str :param mode_func: The function to call if activating the mode :type mode_func: func """ self._clearToggles(name) if self._actions[name].isChecked(): self.mode = mode mode_func() # Force the toolbar text to redraw immediately. Otherwise, we'd have # to wait until the user moves the cursor over the plot. self.set_message(self.mode) def _clearToggles(self, new_name): """ Turn all of the toolbar buttons off except for the specified one :param new_name: The name of the toolbar button to ignore (i.e. the name of the button that was just clicked) :type new_name: str """ for name, action in self._actions.items(): if action.isChecked() and name != new_name: if name == ACTION_NAMES['PAN']: super().pan() elif name == ACTION_NAMES['ZOOM']: super().zoom() else: action.setChecked(False) self.plot.pickNoMode() self.mode = ''
[docs] def pickLabel(self): """ The function corresponding to the Label button """ self._toggleMode('pickLabel', 'Pick to label points', self.plot.pickLabeled)
[docs] def pickSelect(self): """ The function corresponding to the Select button """ self._toggleMode('pickSelect', 'Pick to select entries', self.plot.pickSelected)
[docs] def pickInclude(self): """ The function corresponding to the Include button """ self._toggleMode('pickInclude', 'Pick to include entries', self.plot.pickIncluded)
def _uncheckPickActions(self): """ Uncheck the pick actions (pickLabel, pickSelect, and pickInclude). The `pan` and `zoom` methods automatically handle toggling themselves and each other; however, they are not aware of the pick actions. This method is called in the `pan` or `zoom` method overrides prior to the parent class method to ensure the pick actions are unchecked. """ for name, action in self._actions.items(): if action.isChecked() and name not in ACTION_NAMES.values(): action.setChecked(False) self.plot.pickNoMode()
[docs] def pan(self): """ Toggle pan mode. We override the matplotlib method to make sure that pickLabel, pickSelect, and pickInclude get turned off if they're active. """ self._uncheckPickActions() super().pan()
[docs] def zoom(self): """ Toggle zoom mode. We override the matplotlib method to make sure that pickLabel, pickSelect, and pickInclude get turned off if they're active. """ self._uncheckPickActions() super().zoom()
[docs] def showPT(self): """ Issue Maestro commands to show the project table """ maestro.command("showpanel table")
[docs]class SchrodingerSubplotTool(SubplotToolQt): BETTER_LABELS = { 'left': 'Left Margin', 'right': 'Right Margin', 'top': 'Top Margin', 'bottom': 'Bottom Margin', 'hspace': 'Vertical Gap\nBetween Plots', 'wspace': 'Horizontal Gap\nBetween Plots' }
[docs] def __init__(self, *args): SubplotToolQt.__init__(self, *args) labels = self.findChildren(QtWidgets.QLabel) for label in labels: text = str(label.text()) try: new_label = self.BETTER_LABELS[text] except KeyError: continue label.setText(new_label) label.setAlignment(Qt.AlignCenter)
[docs]class FeedbackSubplotToolQt(SubplotToolQt): """ Allows the parent panel to perform pre and post plot modification actions """
[docs] def __init__(self, targetfig, panel): """ :type panel: AppFramework object :param panel: an object with setWaitCursor and restoreCursor methods """ self.panel = panel SubplotToolQt.__init__(self, targetfig, panel)
[docs] def funcleft(self, val): """ Allow the parent panel to to pre and post plot modification actions """ x, y = self.panel.prePlotAction(what='funcleft') SubplotToolQt.funcleft(self, val) self.panel.postPlotAction(x, y, what='funcleft')
[docs] def funcright(self, val): """ Allow the parent panel to to pre and post plot modification actions """ x, y = self.panel.prePlotAction(what='funcright') SubplotToolQt.funcright(self, val) self.panel.postPlotAction(x, y, what='funcright')
[docs] def funcbottom(self, val): """ Allow the parent panel to to pre and post plot modification actions """ x, y = self.panel.prePlotAction(what='funcbottom') SubplotToolQt.funcbottom(self, val) self.panel.postPlotAction(x, y, what='funcbottom')
[docs] def functop(self, val): """ Allow the parent panel to to pre and post plot modification actions """ x, y = self.panel.prePlotAction(what='functop') SubplotToolQt.functop(self, val) self.panel.postPlotAction(x, y, what='functop')
[docs] def funcwspace(self, val): """ Allow the parent panel to to pre and post plot modification actions """ x, y = self.panel.prePlotAction(what='funcwspace') SubplotToolQt.funcwspace(self, val) self.panel.postPlotAction(x, y, what='funcwspace')
[docs] def funchspace(self, val): """ Allow the parent panel to to pre and post plot modification actions """ x, y = self.panel.prePlotAction(what='funchspace') SubplotToolQt.funchspace(self, val) self.panel.postPlotAction(x, y, what='funchspace')
[docs]class AtomInfoToolbar(NavToolbar): """ Overrides the set_message class of the normal toolbar to enable the following enhancements: 1) Print the atom numbers and their distance. 2) Allow the user to turn off pan/zoom or zoom modes by clicking on the depressed toolbar button 3) Call the parent panel prePlotAction and postPlotAction methods before modifying the plot view in any way to allow the parent to properly prepare for the modification (create a wait cursor, add/remove plot features, etc.) Used by residue_distance_map.py & pose_explorer_dir/multi_canvas_toolbar.py """
[docs] def __init__(self, canvas, parent, coordinates=True, **kwargs): """ Create a new toolbar instance :type canvas: FigureCanvasQTAgg :param canvas: The canvas this toolbar belongs to :type parent: CaDistanceGUI :param parent: The parent panel that contains information about atoms and distances. :type coordinates: bool :param coordinates: not used """ self.panel = parent NavToolbar.__init__(self, canvas, parent, coordinates, **kwargs)
[docs] def set_message(self, event): """ Place the atom numbers and distance between them that corresponds to the atoms currently under the cursor. :type event: mouse move event :param event: the event object generated by the mouse movement """ x, y, axes = event.xdata, event.ydata, event.inaxes # Some messages have status labels at the beginning, so chop off # everything up to x=. if (x is None or y is None or x < 0 or y < 0 or (axes and axes != self.panel.sub_plot)): self.panel.updateStatus() return try: # Translate the plot coordinates into atom numbers and find the # distance between them. xint = int(x) yint = int(y) atomx = self.panel.ca_atoms[xint] atomy = self.panel.ca_atoms[yint] dist = self.panel.distance_matrix[xint, yint] except (IndexError, AttributeError): self.panel.updateStatus() return resx = self.panel.residueName(atomx) resy = self.panel.residueName(atomy) text = "".join([ 'Atom:Residue (x,y) (', str(atomx), ':', resx, ', ', str(atomy), ':', resy, ') Distance=%.1f' % dist ]) self.panel.updateStatus(message=text)
[docs] def getSubplotDialog(self): """ Over-rides NavToolbar method, to initialize FeedbackSubplotToolQt class in order to use the wait cursor when configuring the plot. """ return FeedbackSubplotToolQt(self.canvas.figure, self.parent)
[docs] def mouse_move(self, event): """ Overwrites the parent routine to always call set_message with the x and y data for the event. :type event: mouse move event :param event: the event object generated by the mouse movement """ super()._update_cursor(event) self.set_message(event)
[docs] def isActive(self, actor): """ Checks to see if the action with the name actor active, and if it is, returns it. :type actor: str :param actor: the name of the action to check. The name is what is returned by the action.text() method. :rtype: Action object or False :return: the Action object if it is active, or False if it is not """ for action in self.actions(): if str(action.text()) == actor and action.isChecked(): return action return False