"""
Contains Qt actions specific to the BioLuminate application.
Copyright Schrodinger, LLC. All rights reserved.
"""
#- Imports -------------------------------------------------------------------
import re
from schrodinger.Qt import QtGui
from schrodinger.Qt import QtWidgets
from schrodinger.Qt.QtCore import Qt
#- Globals -------------------------------------------------------------------
ACTION_NAME = 'action_%(name)s'
SEPARATOR_RE = re.compile(r'^separator_\d+')
#- Functions -----------------------------------------------------------------
[docs]def get_action_obj_name(name):
"""
Helper function to get the action's object name according to the syntax
useed to create the name. The syntax is "action_<text>" where text is
the text used when setting the action.
"""
return ACTION_NAME % {'name': name}
[docs]def replace_slot(owner, name, slot):
"""
Removes all slots from the action and replaces it with another slot.
:param owner: The owner of the action
:param name: The name used when creating the `SAction`
:param slot: The new slot to connect to
:type slot: callable
:raise AttributeError: When no action is found with that name in the owner
"""
objname = get_action_obj_name(name)
action = owner.findChild(SAction, objname)
if not action:
msg = 'No action named "%s" was found in %s"' % (objname,
owner.__class__)
raise AttributeError(msg)
# Disconnect the old slots and connect to the new slot
action.disconnectSlots()
action.connectSlot(slot_callable=slot)
#- Classes -------------------------------------------------------------------
[docs]class SAction(QtGui.QAction):
"""
A QAction that enforces standards that are expected by some BioLuminate
methods. The `setObjectName` method is used and the name conforms to
the global `ACTION_NAME` variable in the `factory` module.
"""
[docs] def __init__(self,
parent,
name,
text='',
tooltip='',
status_tip='',
icon=None,
slot=None,
parent_slot=None,
checkable=False,
checked=False,
visible=True,
data=None,
shortcut=None):
"""
Create a SAction object
:param parent: The parent object of this action. This is used for all
`parent_slot` calls.
:param name: This will be used to name the object. The name will have
``action_`` prepended to it.
:type name: str
:param text: The text to use for the action menus and toolbars
:type text: str
:param tooltip: The action's tooltip
:type tooltip: str
:param status_tip: The string to show in the status bar when the action
is hovered over.
:param status_tip: The string to show in the status bar when the action
is hovered over.
:param icon: The icon path used to create a `QtGui.QIcon`
:type icon: string
:param slot: The function called when action is executed
:type slot: callable
:param parent_slot: The name of the parent function called when action
is executed
:type parent_slot: string
:param checkable: Whether action has on/off state.
:type checkable: bool
:param checkable: Whether action is on or off. Only good when
`checkable` is True.
:type checkable: bool
:param visible: Whether the action is shown or hidden
:type visible: bool
:param data: Data associated with action
:type data: mixed
:param shortcut: Shortcut to action
:type shortcut: string
"""
self.parent = parent
super(SAction, self).__init__(parent)
# Setting the objecet name helps out in a few ways.
# PyQT made the object name similar to class lookup
# in a DOM, where you can find an object by name (and type)
# by using findChild(). Multiple objects use findChildren().
self.setObjectName(ACTION_NAME % {'name': name})
if icon:
self.setIcon(QtGui.QIcon(icon))
# The next three paramters are viewable by customers
# so we translate the strings
self.setText(self.tr(text))
self.setToolTip(self.tr(tooltip))
self.setStatusTip(self.tr(status_tip))
# Turn this into a toggle-able action if requested
if checkable:
self.setCheckable(True)
if data is not None:
self.setData(data)
if shortcut:
self.setShortcut(shortcut)
self.setVisible(visible)
# Connect the action to a slot
if slot or parent_slot:
self.connectSlot(slot, parent_slot)
# Set the check state after the action has been connected to
# its slot so the slot is called
if checkable and checked:
self.setChecked(checked)
[docs] def connectSlot(self, slot_callable=None, slot_name=None):
"""
Connects action to a slot by either supplying a callable or a
method name that belongs to the action's parent widget. The
`slot_callable` will override `slot_name` if both are passed in.
:param slot_callable: A function passed in to connect to the action
:type slot_callable: callable
:param slot_name: A method name found in the actions parent
:type slot_name: string
"""
if slot_callable is None:
if hasattr(self.parent, slot_name):
slot_callable = getattr(self.parent, slot_name)
if slot_callable:
if self.isCheckable():
self.toggled.connect(slot_callable)
else:
self.triggered.connect(slot_callable)
[docs] def disconnectSlots(self):
"""
Disconnects slots connected to this action.
"""
if self.isCheckable():
self.toggled.disconnect()
else:
self.triggered.disconnect()
[docs]class Factory:
"""
A class dedicated to the creation, deletion and modification of
groups of actions.
"""
[docs] def __init__(self, parent):
self.parent = parent
self.actions = []
[docs] def connectAction(self, action, slot):
"""
Connects an action to a slot
"""
if action.isCheckable():
action.toggled.connect(slot)
else:
action.triggered.connect(slot)
[docs] def getAction(self, action_name):
"""
Gets an action from the action name
"""
obj_name = ACTION_NAME % {'name': action_name}
action = self.parent.findChild(SAction, obj_name)
return action
[docs] def clearActions(self):
"""
Clears all actions in the factory
"""
self.actions = []
[docs] def removeAction(self, action_name):
"""
Removes an action from the stored actions based on action name
"""
self.actions = [
a for a in self.actions if a.objectName() != action_name
]
[docs] def createActions(self, action_params, action_order=None, parent=None):
"""
Creates actions.
:param action_params: Parameters used to create an action.
:type action_params: dict
:param action_order: Order of actions (must be keys in `action_params`
:type action_order: list of strings
:param parent: Parent of actions. Default: `self.parent`
:type parent: object
:see: `SAction.__init__`
"""
parent = parent or self.parent
action_order = action_order or action_params.get('default_order', [])
if not action_order:
raise Exception('A default_order is needed')
actions = []
for name in action_order:
if SEPARATOR_RE.search(name):
action = QtGui.QAction(parent)
action.setSeparator(True)
else:
params = action_params.get(name)
if params:
action = SAction(parent, name, **params)
actions.append(action)
return actions
[docs] def addActions(self, action_params, action_order=None, parent=None):
"""
Add actions to the current action list
"""
actions = self.createActions(action_params,
action_order=action_order,
parent=parent)
self.actions.extend(actions)
[docs] def setActions(self, action_params, action_order=None, parent=None):
"""
Replace current acction with new actions
"""
actions = self.createActions(action_params,
action_order=action_order,
parent=parent)
self.actions = actions
[docs] def addContext(self, parent=None):
"""
Add a context menu to parent.
:param parent: Parent of actions. Default: `self.parent`
:type parent: object
"""
parent = parent or self.parent
parent.setContextMenuPolicy(Qt.ActionsContextMenu)
parent.addActions(self.actions)