Trigger Notifications

An extension often needs to respond to changes in data caused by other extensions. For instance, in the Colors and Color Wells example, a color well is used to control the color of protein backbone atoms; if another extension (e.g., Midas emulator) changes the color of some backbone atoms, the color in the well should change accordingly as well

Chimera provides the trigger mechanism for notifying interested parties of spontaneous changes in data. A trigger is an object which monitors changes for a set of data; an extension can register a handler to be invoked whenever the trigger detects data modification. A standard set of triggers is defined in chimera.triggers. In particular, there are triggers for objects of common classes; e.g., there is a trigger monitoring all Atom objects. Thus, tracking changes of standard objects is very straightforward. Besides standard object triggers, there are a few other triggers of general interest:

selection changed
This trigger fires whenever the selection in the main graphics window is changed. Functions in chimera.selection are used to query and manipulate the selection.
chimera.APPQUIT
This trigger fires when Chimera is quitting.
chimera.MOTION_START
One or more models have started moving.
chimera.MOTION_STOP
All models have stopped moving for at least a second. Useful for starting heavy-weight calculations/updates based on model positioning.
chimera.CLOSE_SESSION
The user has selected the Close Session menu item.

Also, chimera.openModels maintains two triggers that fire when models are added to or removed from the list of open models. To register for these triggers use the chimera.openModels.addAddHandler and chimera.openModels.addRemoveHandler functions respectively. They each expect two arguments: a callback function and user data. They return a handler ID. The callback function will be invoked with two arguments: the added/removed models and the provided user data. You can deregister from these triggers with chimera.openModels.deleteAddHandler and chimera.openModels.deleteRemoveHandler respectively. These latter two functions expect the handler ID as an argument.

The example below derives from the code in Colors and Color Wells, and the code description assumes that the reader is familiar with that code. While the Colors and Color Wells code only synchronizes the color well with backbone atom colors when the graphical user interface is first created, the example below registers a handler with the Atom trigger and updates the color well whenever a backbone atom is changed. Note that changes in atom data may have nothing to do with colors; the Atom trigger invokes registered handlers whenever any change is made. However, it is computationally cheaper to recompute the well color on any change than to keep track of atom colors and only update the well color on color changes.

Example AtomTrigger/__init__.py

This file is identical to the ColorWellUI/__init__.py in the Colors and Color Wells example.
import chimera
import re

MAINCHAIN = re.compile("^(N|CA|C)$", re.I)
def mainchain(color):

for m in chimera.openModels.list(modelTypes=[chimera.Molecule]):
for a in m.atoms:
if MAINCHAIN.match(a.name):
a.color = color

Example AtomTrigger/gui.py

The code here is very similar to the code in Colors and Color Wells and only differences from that code will be described.

import os
import Tkinter

import chimera
from chimera.baseDialog import ModelessDialog
from chimera.tkoptions import ColorOption
import ColorWellUI

class ColorWellDialog(ModelessDialog):


title = 'Set Backbone Color'

Need to override __init__ to initialize our extra state.
def __init__(self, *args, **kw):

Whereas in the Colors and Color Wells example coloropt was a local variable, here the coloropt variable is stored in the instance because the trigger handler (which has access to the instance) needs to update the color well contained in the ColorOption. A new variable, handlerId, is created to keep track of whether a handler is currently registered. The handler is only created when needed. See map and unmap below. (Note that the instance variables must be set before calling the base __init__ method since the dialog may be mapped during initialization, depending on which window system is used.)
self.colorOpt = None
self.handlerId = None

Call the parent-class __init__.
apply(ModelessDialog.__init__, (self,) + args, kw)


def fillInUI(self, master):

Save ColorOption in instance.
self.coloropt = ColorOption(master, 0, 'Backbone Color', None, self._setBackboneColor, balloon='Protein backbone color')

self._updateBackboneColor()


def _updateBackboneColor(self):
for m in chimera.openModels.list(modelTypes=[chimera.Molecule]):
for a in m.atoms:
if ColorWellUI.MAINCHAIN.match(a.name):
try:
if a.color != theColor:
self.coloropt.setMultiple()
return
except NameError:
theColor = a.color

try:
self.coloropt.set(theColor)
except NameError:
self.coloropt.set(None)

def _setBackboneColor(self, coloroption):
ColorWellUI.mainchain(coloroption.get())

Register a trigger handler to monitor changes in the backbone atom list when we're make visible. We ignore the event argument.
def map(self, *ignore):

Synchronize with well color.
self._updateBackboneColor()

If no handler is currently registered, register one.
if self.handlerId is None:

Registration occurs when the chimera.triggers object is requested to add a handler. Registration requires three arguments:

-
the name of the trigger,
-
the handler function to be invoked when the trigger fires, and
-
an additional argument to be passed to the handler function when it is invoked.

In this case, the trigger name is the same as the name of the class of objects being monitored, "Atom". The handler function is _handler, defined below. And the additional argument is empty (None) -- it could have been the ColorOption instance (coloropt) but that is accessible via the instance. The return value from the registration is a unique handler identifier for the handler/argument combination. This identifier is required for deregistering the handler.

The handler function is always invoked by the trigger with three arguments:

-
the name of the trigger,
-
the additional argument passed in at registration time, and
-
an instance with three attributes
-
created: set of created objects
-
deleted: set of deleted objects
-
modified: set of modified objects

Note that with a newly opened model, objects will just appear in both the created set and not in the modified set, even though the newly created objects will normally have various of their default attributes modified by later code sections.
self.handlerId = chimera.triggers.addHandler('Atom', self._handler, None)

The _handler function is the trigger handler invoked when attributes of Atom instances change.
def _handler(self, trigger, additional, atomChanges):

Check through modified atoms for backbone atoms.
for a in atomChanges.modified:

If any of the changed atoms is a backbone atom, call _updateBackboneColor to synchronize the well color with backbone atom colors.
if ColorWellUI.MAINCHAIN.match(a.name):

self._updateBackboneColor()
return

unmap is called when the dialog disappears. We ignore the event argument.
def unmap(self, *ignore):

Check whether a handler is currently registered (i.e., the handler identifier, handlerId, is not None) and deregister it if necessary.
if self.handlerId is not None:

Deregistration requires two arguments: the name of the trigger and the unique handler identifier returned by the registration call.
chimera.triggers.deleteHandler('Atom', self.handlerId)

Set the unique handler identifier to None to indicate that no handler is currently registered.
self.handlerId = None

Define the module variable dialog, which tracks the dialog instance. It is initialized to None, and is assigned a usable value when the dialog is created.
dialog = None

Define showColorWellUI, which is invoked when the Chimera toolbar button is pressed.
def showColorWellUI():

global dialog
if dialog is not None:
dialog.enter()
return

dialog = ColorWellDialog()

dir, file = os.path.split(__file__)
icon = os.path.join(dir, 'AtomTrigger.tiff')
chimera.tkgui.app.toolbar.add(icon, showColorWellUI, 'Set Main Chain Color', None)

Code Notes

Monitoring changes in atoms can result in many handler invocations. In an attempt to reduce computation, the example above deregisters its handler when the user interface is not being displayed.

Running the Example

The example code files must be saved in a directory named AtomTrigger. To run the example, start chimera, bring up the Tools preference category (via the Preferences entry in the Favorites menu; then choosing the "Tools" preference category) and use the Add button to add the directory above the AtomTrigger directory. Then type the following command into the IDLE command line:

import AtomTrigger.gui

This should display a button on the Chimera toolbar. Clicking the button should bring up a window with a color well inside. The color well may be used to manipulate the color of all protein backbone atoms. Changing atom colors through another mechanism, e.g., the Midas emulator, should result in appropriate color changes in the color well.