/* $RCSfile$
* $Author: hansonr $
* $Date: 2009-07-31 09:22:19 -0500 (Fri, 31 Jul 2009) $
* $Revision: 11291 $
*
* Copyright (C) 2002-2005 The Jmol Development Team
*
* Contact: jmol-developers@lists.sf.net
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.jmol.viewer;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;
import javax.vecmath.Point3f;
import org.jmol.g3d.Graphics3D;
import org.jmol.i18n.GT;
import org.jmol.modelset.Atom;
import org.jmol.modelset.AtomCollection;
import org.jmol.modelset.MeasurementPending;
import org.jmol.script.ScriptEvaluator;
import org.jmol.script.Token;
import org.jmol.util.BitSetUtil;
import org.jmol.util.Escape;
import org.jmol.util.Logger;
import org.jmol.util.Point3fi;
import org.jmol.util.TextFormat;
import org.jmol.viewer.binding.DragBinding;
import org.jmol.viewer.binding.Binding;
import org.jmol.viewer.binding.PfaatBinding;
import org.jmol.viewer.binding.RasmolBinding;
import org.jmol.viewer.binding.JmolBinding;
public class ActionManager {
public final static int ACTION_center = 0;
public final static int ACTION_translate = 1;
public final static int ACTION_rotate = 2;
public final static int ACTION_rotateZ = 3;
public final static int ACTION_rotateZorZoom = 4;
public final static int ACTION_wheelZoom = 5;
public final static int ACTION_slideZoom = 6;
public final static int ACTION_navTranslate = 7;
public final static int ACTION_swipe = 8;
public final static int ACTION_spinDrawObjectCW = 9;
public final static int ACTION_spinDrawObjectCCW = 10;
public final static int ACTION_slab = 11;
public final static int ACTION_depth = 12;
public final static int ACTION_slabAndDepth = 13;
public final static int ACTION_popupMenu = 14;
public final static int ACTION_clickFrank = 15;
public final static int ACTION_select = 16;
public final static int ACTION_selectNone = 17;
public final static int ACTION_selectToggle = 18;
public final static int ACTION_selectAndNot = 19;
public final static int ACTION_selectOr = 20;
public final static int ACTION_selectToggleExtended = 21;
public final static int ACTION_dragSelected = 22;
public final static int ACTION_selectAndDrag = 23;
public final static int ACTION_rotateSelected = 24;
public final static int ACTION_rotateBranch = 25;
public final static int ACTION_dragAtom = 26;
public final static int ACTION_dragMinimize = 27;
public final static int ACTION_dragMinimizeMolecule = 28;
public final static int ACTION_dragLabel = 29;
public final static int ACTION_dragDrawPoint = 30;
public final static int ACTION_dragDrawObject = 31;
public final static int ACTION_pickAtom = 32;
public final static int ACTION_pickPoint = 33;
public final static int ACTION_pickLabel = 34;
public final static int ACTION_pickMeasure = 35;
public final static int ACTION_setMeasure = 36;
public final static int ACTION_pickIsosurface = 37;
public final static int ACTION_pickNavigate = 38;
public final static int ACTION_deleteAtom = 39;
public final static int ACTION_deleteBond = 40;
public final static int ACTION_connectAtoms = 41;
public final static int ACTION_assignNew = 42;
public final static int ACTION_reset = 43;
public final static int ACTION_stopMotion = 44;
public final static int ACTION_multiTouchSimulation = 45;
public final static int ACTION_count = 46;
private final static String[] actionInfo = new String[ACTION_count];
private final static String[] actionNames = new String[ACTION_count];
static void newAction(int i, String name, String info) {
actionInfo[i] = info;
actionNames[i] = name;
}
static {
newAction(ACTION_center, "_center", GT._("center"));
newAction(ACTION_translate, "_translate", GT._("translate"));
newAction(ACTION_rotate, "_rotate", GT._("rotate"));
newAction(ACTION_rotateZ, "_rotateZ", GT._("rotate Z"));
newAction(ACTION_rotateZorZoom, "_rotateZorZoom", GT._("rotate Z (horizontal motion of mouse) or zoom (vertical motion of mouse)"));
newAction(ACTION_wheelZoom, "_wheelZoom", GT._("zoom"));
newAction(ACTION_slideZoom, "_slideZoom", GT._("zoom (along right edge of window)"));
newAction(ACTION_navTranslate, "_navTranslate", GT._("translate navigation point (requires {0} and {1})", new String[] {"set NAVIGATIONMODE", "set picking NAVIGATE"}));
newAction(ACTION_swipe, "_swipe", GT._("spin model (swipe and release button and stop motion simultaneously)"));
newAction(ACTION_spinDrawObjectCW, "_spinDrawObjectCW", GT._("click on two points to spin around axis clockwise (requires {0})", "set picking SPIN"));
newAction(ACTION_spinDrawObjectCCW, "_spinDrawObjectCCW", GT._("click on two points to spin around axis counterclockwise (requires {0})", "set picking SPIN"));
newAction(ACTION_slab, "_slab", GT._("adjust slab (front plane; requires {0})", "SLAB ON"));
newAction(ACTION_depth, "_depth", GT._("adjust depth (back plane; requires {0})", "SLAB ON"));
newAction(ACTION_slabAndDepth, "_slabAndDepth", GT._("move slab/depth window (both planes; requires {0})", "SLAB ON"));
newAction(ACTION_popupMenu, "_popupMenu", GT._("pop up the full context menu"));
newAction(ACTION_clickFrank, "_clickFrank", GT._("pop up recent context menu (click on Jmol frank)"));
newAction(ACTION_select, "_select", GT._("select an atom (requires {0})", "set pickingStyle EXTENDEDSELECT"));
newAction(ACTION_selectNone, "_selectNone", GT._("select NONE (requires {0})", "set pickingStyle EXTENDEDSELECT"));
newAction(ACTION_selectToggle, "_selectToggle", GT._("toggle selection (requires {0})", "set pickingStyle DRAG/EXTENDEDSELECT/RASMOL"));
newAction(ACTION_selectAndNot, "_selectAndNot", GT._("unselect this group of atoms (requires {0})", "set pickingStyle DRAG/EXTENDEDSELECT"));
newAction(ACTION_selectOr, "_selectOr", GT._("add this group of atoms to the set of selected atoms (requires {0})", "set pickingStyle DRAG/EXTENDEDSELECT"));
newAction(ACTION_selectToggleExtended, "_selectToggleOr", GT._("if all are selected, unselect all, otherwise add this group of atoms to the set of selected atoms (requires {0})", "set pickingStyle DRAG"));
newAction(ACTION_dragSelected, "_dragSelected", GT._("move selected atoms (requires {0})", "set DRAGSELECTED"));
newAction(ACTION_selectAndDrag, "_selectAndDrag", GT._("select and drag atoms (requires {0})", "set DRAGSELECTED"));
newAction(ACTION_rotateSelected, "_rotateSelected", GT._("rotate selected atoms (requires {0})", "set DRAGSELECTED"));
newAction(ACTION_rotateBranch, "_rotateBranch", GT._("rotate branch around bond (requires {0})", "set picking ROTATEBOND"));
newAction(ACTION_dragAtom, "_dragAtom", GT._("move atom (requires {0})", "set picking DRAGATOM"));
newAction(ACTION_dragMinimize, "_dragMinimize", GT._("move atom and minimize molecule (requires {0})", "set picking DRAGMINIMIZE"));
newAction(ACTION_dragMinimizeMolecule, "_dragMinimizeMolecule", GT._("move and minimize molecule (requires {0})", "set picking DRAGMINIMIZEMOLECULE"));
newAction(ACTION_dragLabel, "_dragLabel", GT._("move label (requires {0})", "set picking LABEL"));
newAction(ACTION_dragDrawPoint, "_dragDrawPoint", GT._("move specific DRAW point (requires {0})", "set picking DRAW"));
newAction(ACTION_dragDrawObject, "_dragDrawObject", GT._("move whole DRAW object (requires {0})", "set picking DRAW"));
newAction(ACTION_pickAtom, "_pickAtom", GT._("pick an atom"));
newAction(ACTION_pickPoint, "_pickPoint", GT._("pick a DRAW point (for measurements) (requires {0}", "set DRAWPICKING"));
newAction(ACTION_pickLabel, "_pickLabel", GT._("pick a label to toggle it hidden/displayed (requires {0})", "set picking LABEL"));
newAction(ACTION_pickMeasure, "_pickMeasure", GT._("pick an atom to include it in a measurement (after starting a measurement or after {0})", "set picking DISTANCE/ANGLE/TORSION"));
newAction(ACTION_setMeasure, "_setMeasure", GT._("pick an atom to initiate or conclude a measurement"));
newAction(ACTION_pickIsosurface, "_pickIsosurface", GT._("pick an ISOSURFACE point (requires {0}", "set DRAWPICKING"));
newAction(ACTION_pickNavigate, "_pickNavigate", GT._("pick a point or atom to navigate to (requires {0})", "set NAVIGATIONMODE"));
newAction(ACTION_deleteAtom, "_deleteAtom", GT._("delete atom (requires {0})", "set picking DELETE ATOM"));
newAction(ACTION_deleteBond, "_deleteBond", GT._("delete bond (requires {0})", "set picking DELETE BOND"));
newAction(ACTION_connectAtoms, "_pickConnect", GT._("connect atoms (requires {0})", "set picking CONNECT"));
newAction(ACTION_assignNew, "_assignNew", GT._("assign/new atom or bond (requires {0})", "set picking assignAtom_??/assignBond_?"));
newAction(ACTION_reset, "_reset", GT._("reset (when clicked off the model)"));
newAction(ACTION_stopMotion, "_stopMotion", GT._("stop motion (requires {0})", "set waitForMoveTo FALSE"));
newAction(ACTION_multiTouchSimulation, "_multiTouchSimulation", GT._("simulate multi-touch using the mouse)"));
}
public static String getActionName(int i) {
return (i < actionNames.length ? actionNames[i] : null);
}
public static int getActionFromName(String name) {
for (int i = 0; i < actionNames.length; i++)
if (actionNames[i].equalsIgnoreCase(name))
return i;
return -1;
}
public String getBindingInfo(String qualifiers) {
return binding.getBindingInfo(actionInfo, qualifiers);
}
/**
* picking modes select picking....
*/
public final static int PICKING_OFF = 0;
public final static int PICKING_IDENTIFY = 1;
public final static int PICKING_LABEL = 2;
public final static int PICKING_CENTER = 3;
public final static int PICKING_DRAW = 4;
public final static int PICKING_SPIN = 5;
public final static int PICKING_SYMMETRY = 6;
public final static int PICKING_DELETE_ATOM = 7;
public final static int PICKING_DELETE_BOND = 8;
public final static int PICKING_SELECT_ATOM = 9;
public final static int PICKING_SELECT_GROUP = 10;
public final static int PICKING_SELECT_CHAIN = 11;
public final static int PICKING_SELECT_MOLECULE = 12;
public final static int PICKING_SELECT_POLYMER = 13;
public final static int PICKING_SELECT_STRUCTURE = 14;
public final static int PICKING_SELECT_SITE = 15;
public final static int PICKING_SELECT_MODEL = 16;
public final static int PICKING_SELECT_ELEMENT = 17;
public final static int PICKING_MEASURE = 18;
public final static int PICKING_MEASURE_DISTANCE = 19;
public final static int PICKING_MEASURE_ANGLE = 20;
public final static int PICKING_MEASURE_TORSION = 21;
public final static int PICKING_MEASURE_SEQUENCE = 22;
public final static int PICKING_NAVIGATE = 23;
public final static int PICKING_CONNECT = 24;
public final static int PICKING_STRUTS = 25;
public final static int PICKING_DRAG_MOLECULE = 26;
public final static int PICKING_DRAG_ATOM = 27;
public final static int PICKING_DRAG_MINIMIZE = 28;
public final static int PICKING_DRAG_MINIMIZE_MOLECULE = 29; // for docking
public final static int PICKING_INVERT_STEREO = 30;
public final static int PICKING_ASSIGN_ATOM = 31;
public final static int PICKING_ASSIGN_BOND = 32;
public final static int PICKING_ROTATE_BOND = 33;
public final static int PICKING_IDENTIFY_BOND = 34;
private final static String[] pickingModeNames = {
"off", "identify", "label", "center", "draw", "spin",
"symmetry", "deleteatom", "deletebond",
"atom", "group", "chain", "molecule", "polymer", "structure",
"site", "model", "element",
"measure", "distance", "angle", "torsion", "sequence",
"navigate",
"connect", "struts",
"dragmolecule", "dragatom", "dragminimize", "dragminimizemolecule",
"invertstereo", "assignatom", "assignbond", "rotatebond", "identifybond"
};
public final static String getPickingModeName(int pickingMode) {
return (pickingMode < 0 || pickingMode >= pickingModeNames.length ? "off"
: pickingModeNames[pickingMode]);
}
public final static int getPickingMode(String str) {
for (int i = pickingModeNames.length; --i >= 0; )
if (str.equalsIgnoreCase(pickingModeNames[i]))
return i;
return -1;
}
/**
* picking styles
*/
public final static int PICKINGSTYLE_SELECT_JMOL = 0;
public final static int PICKINGSTYLE_SELECT_CHIME = 0;
public final static int PICKINGSTYLE_SELECT_RASMOL = 1;
public final static int PICKINGSTYLE_SELECT_PFAAT = 2;
public final static int PICKINGSTYLE_SELECT_DRAG = 3;
public final static int PICKINGSTYLE_MEASURE_ON = 4;
public final static int PICKINGSTYLE_MEASURE_OFF = 5;
private final static String[] pickingStyleNames = {
"toggle", "selectOrToggle", "extendedSelect", "drag",
"measure", "measureoff"
};
public final static String getPickingStyleName(int pickingStyle) {
return (pickingStyle < 0 || pickingStyle >= pickingStyleNames.length ? "toggle"
: pickingStyleNames[pickingStyle]);
}
public final static int getPickingStyle(String str) {
for (int i = pickingStyleNames.length; --i >= 0; )
if (str.equalsIgnoreCase(pickingStyleNames[i]))
return i;
return -1;
}
public Hashtable getMouseInfo() {
Hashtable info = new Hashtable();
List vb = new ArrayList();
Enumeration e = binding.getBindings().elements();
while (e.hasMoreElements()) {
Object obj = e.nextElement();
if (obj instanceof Boolean)
continue;
if (obj instanceof int[]) {
int[] binding = (int[]) obj;
obj = new String[] { Binding.getMouseActionName(binding[0], false),
getActionName(binding[1]) };
}
vb.add(obj);
}
info.put("bindings", vb);
info.put("bindingName", binding.getName());
info.put("actionNames", actionNames);
info.put("actionInfo", actionInfo);
info.put("bindingInfo", TextFormat.split(getBindingInfo(null), '\n'));
return info;
}
private final static long MAX_DOUBLE_CLICK_MILLIS = 700;
protected final static long MININUM_GESTURE_DELAY_MILLISECONDS = 5;
private final static int SLIDE_ZOOM_X_PERCENT = 98;
public final static float DEFAULT_MOUSE_DRAG_FACTOR = 1f;
public final static float DEFAULT_MOUSE_WHEEL_FACTOR = 1.02f;
public final static float DEFAULT_GESTURE_SWIPE_FACTOR = 1f;
protected Viewer viewer;
protected Binding binding;
Binding jmolBinding;
Binding pfaatBinding;
Binding dragBinding;
Binding rasmolBinding;
Binding predragBinding;
public void setViewer(Viewer viewer, String commandOptions) {
this.viewer = viewer;
setBinding(jmolBinding = new JmolBinding());
}
boolean isBound(int gesture, int action) {
return binding.isBound(gesture, action);
}
void bindAction(String desc, String name, Point3f range1,
Point3f range2) {
int jmolAction = getActionFromName(name);
int mouseAction = Binding.getMouseAction(desc);
if (mouseAction == 0)
return;
if (jmolAction >= 0) {
binding.bind(mouseAction, jmolAction);
} else {
binding.bind(mouseAction, name);
}
}
protected void clearBindings() {
setBinding(jmolBinding = new JmolBinding());
pfaatBinding = null;
dragBinding = null;
rasmolBinding = null;
}
void unbindAction(String desc, String name) {
if (desc == null && name == null) {
clearBindings();
return;
}
int jmolAction = getActionFromName(name);
int mouseAction = Binding.getMouseAction(desc);
if (jmolAction >= 0)
binding.unbind(mouseAction, jmolAction);
else if (mouseAction != 0)
binding.unbind(mouseAction, name);
if (name == null)
binding.unbindUserAction(desc);
}
protected Thread hoverWatcherThread;
protected boolean haveMultiTouchInput = false;
protected int xyRange = 0;
private float gestureSwipeFactor = DEFAULT_GESTURE_SWIPE_FACTOR;
protected float mouseDragFactor = DEFAULT_MOUSE_DRAG_FACTOR;
protected float mouseWheelFactor = DEFAULT_MOUSE_WHEEL_FACTOR;
void setGestureSwipeFactor(float factor) {
gestureSwipeFactor = factor;
}
void setMouseDragFactor(float factor) {
mouseDragFactor = factor;
}
void setMouseWheelFactor(float factor) {
mouseWheelFactor = factor;
}
protected class Mouse {
protected int x = -1000;
protected int y = -1000;
protected int modifiers = 0;
protected long time = -1;
protected void set(long time, int x, int y, int modifiers) {
this.time = time;
this.x = x;
this.y = y;
this.modifiers = modifiers;
}
protected void setCurrent() {
time = current.time;
x = current.x;
y = current.y;
modifiers = current.modifiers;
}
public boolean inRange(int x, int y) {
return (Math.abs(this.x - x) <= xyRange && Math.abs(this.y - y) <= xyRange);
}
public boolean check(int x, int y, int modifiers, long time, long delayMax) {
return (inRange(x, y) && this.modifiers == modifiers && (time - this.time) < delayMax);
}
}
protected final Mouse current = new Mouse();
protected final Mouse moved = new Mouse();
private final Mouse clicked = new Mouse();
private final Mouse pressed = new Mouse();
private final Mouse dragged = new Mouse();
protected void setCurrent(long time, int x, int y, int mods) {
hoverOff();
current.set(time, x, y, mods);
}
int getCurrentX() {
return current.x;
}
int getCurrentY() {
return current.y;
}
protected int pressedCount;
private int pressedAtomIndex;
protected int clickedCount;
private boolean drawMode = false;
private boolean labelMode = false;
private boolean dragSelectedMode = false;
private boolean measuresEnabled = true;
public void setMouseMode() {
drawMode = labelMode = false;
dragSelectedMode = viewer.getDragSelected();
measuresEnabled = !dragSelectedMode;
if (!dragSelectedMode)
switch (atomPickingMode) {
default:
return;
case PICKING_ASSIGN_ATOM:
measuresEnabled = !isPickAtomAssignCharge;
return;
case PICKING_DRAW:
drawMode = true;
// drawMode and dragSelectedMode are incompatible
measuresEnabled = false;
break;
//other cases here?
case PICKING_LABEL:
labelMode = true;
measuresEnabled = false;
break;
case PICKING_SELECT_ATOM:
case PICKING_MEASURE_DISTANCE:
case PICKING_MEASURE_SEQUENCE:
case PICKING_MEASURE_ANGLE:
case PICKING_MEASURE_TORSION:
measuresEnabled = false;
break;
}
exitMeasurementMode();
}
protected void clearMouseInfo() {
// when a second touch is made, this clears all record of first touch
pressedCount = clickedCount = 0;
dragGesture.setAction(0, 0);
exitMeasurementMode();
}
private boolean hoverActive = false;
private MeasurementPending measurementPending;
private int dragAtomIndex = -1;
private boolean rubberbandSelectionMode = false;
private final Rectangle rectRubber = new Rectangle();
private boolean isAltKeyReleased = true;
private boolean keyProcessing;
protected boolean isMultiTouchClient;
protected boolean isMultiTouchServer;
public boolean isMTClient() {
return isMultiTouchClient;
}
public boolean isMTServer() {
return isMultiTouchServer;
}
public void dispose() {
clear();
}
public void clear() {
startHoverWatcher(false);
clearTimeouts();
if (predragBinding != null)
binding = predragBinding;
viewer.setPickingMode(null, PICKING_IDENTIFY);
viewer.setPickingStyle(null, rootPickingStyle);
eval = null;
isAltKeyReleased = true;
}
synchronized public void startHoverWatcher(boolean isStart) {
if (viewer.isPreviewOnly())
return;
try {
if (isStart) {
if (hoverWatcherThread != null)
return;
current.time = -1;
hoverWatcherThread = new Thread(new HoverWatcher());
hoverWatcherThread.setName("HoverWatcher");
hoverWatcherThread.start();
} else {
if (hoverWatcherThread == null)
return;
current.time = -1;
hoverWatcherThread.interrupt();
hoverWatcherThread = null;
}
} catch (Exception e) {
// is possible -- seen once hoverWatcherThread.start() had null pointer.
}
}
public void setModeMouse(int modeMouse) {
if (modeMouse == JmolConstants.MOUSE_NONE) {
startHoverWatcher(false);
}
}
/**
* called by MouseManager.keyPressed
* @param ke
*/
public void keyPressed(KeyEvent ke) {
ke.consume();
if (keyProcessing)
return;
hoverOff();
//System.out.println("ActionmManager keyPressed: " + ke.getKeyCode());
keyProcessing = true;
int i = ke.getKeyCode();
switch(i) {
case KeyEvent.VK_ALT:
if (dragSelectedMode && isAltKeyReleased)
viewer.moveSelected(Integer.MIN_VALUE, 0, Integer.MIN_VALUE, Integer.MIN_VALUE, null, false, false);
isAltKeyReleased = false;
moved.modifiers |= Binding.ALT;
break;
case KeyEvent.VK_SHIFT:
dragged.modifiers |= Binding.SHIFT;
moved.modifiers |= Binding.SHIFT;
break;
case KeyEvent.VK_CONTROL:
moved.modifiers |= Binding.CTRL;
}
int action = Binding.LEFT+Binding.SINGLE_CLICK+moved.modifiers;
if(!labelMode && !binding.isUserAction(action) && !isSelectAction(action))
checkMotionRotateZoom(action, current.x, 0, 0, false);
if (viewer.getNavigationMode()) {
int m = ke.getModifiers();
// if (viewer.getBooleanProperty("showKeyStrokes", false))
// viewer.evalStringQuiet("!set echo bottom left;echo "
// + (i == 0 ? "" : i + " " + m));
switch (i) {
case KeyEvent.VK_UP:
case KeyEvent.VK_DOWN:
case KeyEvent.VK_LEFT:
case KeyEvent.VK_RIGHT:
case KeyEvent.VK_SPACE:
case KeyEvent.VK_PERIOD:
viewer.navigate(i, m);
break;
}
}
keyProcessing = false;
}
public void keyReleased(KeyEvent ke) {
//System.out.println("ActionmManager keyReleased: " + ke.getKeyCode());
ke.consume();
int i = ke.getKeyCode();
switch(i) {
case KeyEvent.VK_ALT:
if (dragSelectedMode)
viewer.moveSelected(Integer.MAX_VALUE, 0, Integer.MIN_VALUE, Integer.MIN_VALUE, null, false, false);
isAltKeyReleased = true;
moved.modifiers &= ~Binding.ALT;
break;
case KeyEvent.VK_SHIFT:
moved.modifiers &= ~Binding.SHIFT;
break;
case KeyEvent.VK_CONTROL:
moved.modifiers &= ~Binding.CTRL;
}
if (moved.modifiers == 0)
viewer.setCursor(Viewer.CURSOR_DEFAULT);
if (!viewer.getNavigationMode())
return;
//if (viewer.getBooleanProperty("showKeyStrokes", false))
//viewer.evalStringQuiet("!set echo bottom left;echo;");
switch (i) {
case KeyEvent.VK_UP:
case KeyEvent.VK_DOWN:
case KeyEvent.VK_LEFT:
case KeyEvent.VK_RIGHT:
viewer.navigate(0, 0);
break;
}
}
public void mouseEntered(long time, int x, int y) {
setCurrent(time, x, y, 0);
}
public void mouseExited(long time, int x, int y) {
setCurrent(time, x, y, 0);
exitMeasurementMode();
}
public void mouseAction(int action, long time, int x, int y, int count,
int modifiers) {
if (!viewer.getMouseEnabled())
return;
switch (action) {
case Binding.MOVED:
setCurrent(time, x, y, modifiers);
moved.setCurrent();
if (measurementPending != null || hoverActive)
checkPointOrAtomClicked(x, y, 0, 0, false, Binding.MOVED);
else if (isZoomArea(x))
checkMotionRotateZoom(Binding.getMouseAction(1, Binding.LEFT), 0, 0, 0, false);
else if (viewer.getCursor() == Viewer.CURSOR_ZOOM)//if (dragSelectedMode)
viewer.setCursor(Viewer.CURSOR_DEFAULT);
return;
case Binding.WHEELED:
if (viewer.isApplet() && !viewer.hasFocus())
return;
// sun bug? noted by Charles Xie that wheeling on a Java page
// effected inappropriate wheeling on this Java component
setCurrent(time, current.x, current.y, modifiers);
checkAction(Binding.getMouseAction(0, modifiers), current.x, current.y, 0, y, time, Binding.WHEELED);
return;
case Binding.CLICKED:
setMouseMode();
setCurrent(time, x, y, modifiers);
clickedCount = (count > 1 ? count : clicked.check(x, y, modifiers, time,
MAX_DOUBLE_CLICK_MILLIS) ? clickedCount + 1 : 1);
clicked.setCurrent();
viewer.setFocus();
if (atomPickingMode != PICKING_SELECT_ATOM && isBound(Binding.getMouseAction(Integer.MIN_VALUE, modifiers), ACTION_selectAndDrag))
return;
checkPointOrAtomClicked(x, y, modifiers, clickedCount, false, Binding.CLICKED);
return;
case Binding.DRAGGED:
setMouseMode();
int deltaX = x - dragged.x;
int deltaY = y - dragged.y;
setCurrent(time, x, y, modifiers);
dragged.setCurrent();
if (atomPickingMode != PICKING_ASSIGN_ATOM)
exitMeasurementMode();
action = Binding.getMouseAction(pressedCount, modifiers);
dragGesture.add(action, x, y, time);
checkAction(action, x, y, deltaX, deltaY, time, Binding.DRAGGED);
return;
case Binding.PRESSED:
setCurrent(time, x, y, modifiers);
pressedCount = (pressed.check(x, y, modifiers, time, MAX_DOUBLE_CLICK_MILLIS) ? pressedCount + 1
: 1);
pressed.setCurrent();
dragged.setCurrent();
viewer.setFocus();
boolean isSelectAndDrag = isBound(Binding.getMouseAction(Integer.MIN_VALUE,
modifiers), ACTION_selectAndDrag);
action = Binding.getMouseAction(pressedCount, modifiers);
dragGesture.setAction(action, time);
if (Binding.getModifiers(action) != 0) {
action = viewer.notifyMouseClicked(x, y, action, Binding.PRESSED);
if (action == 0)
return;
}
pressedAtomIndex = Integer.MAX_VALUE;
if (checkUserAction(action, x, y, 0, 0, time, 0))
return;
if (drawMode
&& (isBound(action, ACTION_dragDrawObject) || isBound(action,
ACTION_dragDrawPoint)) || labelMode
&& isBound(action, ACTION_dragLabel)) {
viewer.checkObjectDragged(Integer.MIN_VALUE, 0, x, y, action);
return;
}
boolean isBound = false;
switch (atomPickingMode) {
case PICKING_ASSIGN_ATOM:
isBound = isBound(action, ACTION_assignNew);
break;
case PICKING_DRAG_ATOM:
isBound = isBound(action, ACTION_dragAtom);
break;
case PICKING_DRAG_MOLECULE:
isBound = isBound(action, ACTION_dragAtom) || isBound(action,
ACTION_rotateSelected);
break;
case PICKING_DRAG_MINIMIZE:
isBound = isBound(action, ACTION_dragMinimize);
break;
case PICKING_DRAG_MINIMIZE_MOLECULE:
isBound = isBound(action, ACTION_dragMinimizeMolecule) || isBound(action,
ACTION_rotateSelected);
break;
}
if (isBound) {
dragAtomIndex = viewer.findNearestAtomIndex(x, y, true);
if (dragAtomIndex >= 0
&& (atomPickingMode == PICKING_ASSIGN_ATOM || atomPickingMode == PICKING_INVERT_STEREO)
&& viewer.isAtomAssignable(dragAtomIndex)) {
enterMeasurementMode();
measurementPending.addPoint(dragAtomIndex, null, false);
}
return;
}
if (dragSelectedMode) {
haveSelection = true;
if (isSelectAndDrag) {
haveSelection = (viewer.findNearestAtomIndex(x, y, true) >= 0);
// checkPointOrAtomClicked(x, y, modifiers, pressedCount, true);
}
if (isBound(action, ACTION_dragSelected) && haveSelection) {
viewer.moveSelected(Integer.MIN_VALUE, 0, Integer.MIN_VALUE,
Integer.MIN_VALUE, null, false, false);
}
return;
}
if (isBound(action, ACTION_popupMenu)) {
char type = 'j';
if (viewer.getModelkitMode()) {
Token t = viewer.checkObjectClicked(x, y, Binding.getMouseAction(1,
Binding.LEFT));
type = (t != null && t.tok == Token.bonds ? 'b' : viewer
.findNearestAtomIndex(x, y) >= 0 ? 'a' : 'm');
}
viewer.popupMenu(x, y, type);
return;
}
if (viewer.useArcBall())
viewer.rotateArcBall(x, y, 0);
checkMotionRotateZoom(action, x, 0, 0, true);
return;
case Binding.RELEASED:
setCurrent(time, x, y, modifiers);
viewer.spinXYBy(0, 0, 0);
boolean dragRelease = !pressed.check(x, y, modifiers, time, Long.MAX_VALUE);
viewer.setInMotion(false);
viewer.setCursor(Viewer.CURSOR_DEFAULT);
action = Binding.getMouseAction(pressedCount, modifiers);
dragGesture.add(action, x, y, time);
if (dragRelease)
viewer.setRotateBondIndex(Integer.MIN_VALUE);
if (dragAtomIndex >= 0) {
if (atomPickingMode == PICKING_DRAG_MINIMIZE || atomPickingMode == PICKING_DRAG_MINIMIZE_MOLECULE)
minimize(true);
}
if (atomPickingMode == PICKING_ASSIGN_ATOM
&& isBound(action, ACTION_assignNew)) {
if (measurementPending == null || dragAtomIndex < 0)
return;
// H C + -, etc.
// also check valence and add/remove H atoms as necessary?
if (measurementPending.getCount() == 2) {
viewer.undoAction(-1, Viewer.ACTION_SAVE, true);
viewer.script("assign connect "
+ measurementPending.getMeasurementScript(" ", false));
} else if (pickAtomAssignType.equals("Xx")) {
exitMeasurementMode();
viewer.refresh(3, "bond dropped");
} else {
if (pressed.inRange(dragged.x, dragged.y)) {
String s = "assign atom ({" + dragAtomIndex + "}) \""
+ pickAtomAssignType + "\"";
if (isPickAtomAssignCharge) {
s += ";{atomindex=" + dragAtomIndex + "}.label='%C'; ";
viewer.undoAction(dragAtomIndex,
AtomCollection.TAINT_FORMALCHARGE, true);
} else {
viewer.undoAction(-1, Viewer.ACTION_SAVE, true);
}
viewer.script(s);
} else if (!isPickAtomAssignCharge) {
viewer.undoAction(-1, Viewer.ACTION_SAVE, true);
Atom a = viewer.getModelSet().atoms[dragAtomIndex];
if (a.getElementNumber() == 1) {
viewer.script("assign atom ({" + dragAtomIndex + "}) \"X\"");
} else {
Point3f ptNew = new Point3f(x, y, a.screenZ);
viewer.unTransformPoint(ptNew, ptNew);
viewer.script("assign atom ({" + dragAtomIndex + "}) \""
+ pickAtomAssignType + "\" " + Escape.escape(ptNew));
}
}
}
exitMeasurementMode();
return;
}
dragAtomIndex = -1;
boolean isRbAction = isRubberBandSelect(action);
if (isRbAction) {
BitSet bs = viewer.findAtomsInRectangle(rectRubber);
if (bs.length() > 0) {
String s = Escape.escape(bs);
if (isBound(action, ACTION_selectOr))
viewer.script("selectionHalos on;select selected or " + s);
else if (isBound(action, ACTION_selectAndNot))
viewer.script("selectionHalos on;select selected and not " + s);
else
// ACTION_selectToggle
viewer.script("selectionHalos on;select selected tog " + s);
}
viewer.refresh(3, "mouseReleased");
}
rubberbandSelectionMode = (binding.getName() == "drag");
rectRubber.x = Integer.MAX_VALUE;
if (dragRelease) {
viewer.notifyMouseClicked(x, y, Binding.getMouseAction(pressedCount, 0), Binding.RELEASED);
}
if (drawMode
&& (isBound(action, ACTION_dragDrawObject) || isBound(action,
ACTION_dragDrawPoint)) || labelMode
&& isBound(action, ACTION_dragLabel)) {
viewer.checkObjectDragged(Integer.MAX_VALUE, 0, x, y, action);
return;
}
if (dragSelectedMode && isBound(action, ACTION_dragSelected)
&& haveSelection)
viewer.moveSelected(Integer.MAX_VALUE, 0, Integer.MIN_VALUE,
Integer.MIN_VALUE, null, false, false);
if (dragRelease && checkUserAction(action, x, y, 0, 0, time, 2))
return;
if (viewer.getAllowGestures()) {
if (isBound(action, ACTION_swipe)) {
float speed = getExitRate();
if (speed > 0)
viewer.spinXYBy(dragGesture.getDX(4, 2), dragGesture.getDY(4, 2),
speed * 30 * gestureSwipeFactor);
if (viewer.getLogGestures())
viewer.log("$NOW$ swipe " + dragGesture + " " + speed);
return;
}
}
return;
}
}
private boolean haveSelection;
public void mouseReleased(long time, int x, int y, int modifiers) {
}
private void minimize(boolean dragDone) {
viewer.stopMinimization();
if (dragDone)
dragAtomIndex = -1;
BitSet bs = (viewer.getMotionFixedAtoms().cardinality() == 0 ? viewer
.getAtomBits((viewer.isAtomPDB(dragAtomIndex) ? Token.group
: Token.molecule), BitSetUtil.setBit(dragAtomIndex)) : BitSetUtil
.setAll(viewer.getAtomCount()));
viewer.minimize(Integer.MAX_VALUE, 0, bs, null, 0, false, false, false);
}
protected float getExitRate() {
long dt = dragGesture.getTimeDifference(2);
return (dt > MININUM_GESTURE_DELAY_MILLISECONDS ? 0 :
dragGesture.getSpeedPixelsPerMillisecond(4, 2));
}
private boolean isRubberBandSelect(int action) {
return rubberbandSelectionMode &&
( isBound(action, ACTION_selectToggle)
|| isBound(action, ACTION_selectOr)
|| isBound(action, ACTION_selectAndNot)
);
}
public Rectangle getRubberBand() {
if (!rubberbandSelectionMode || rectRubber.x == Integer.MAX_VALUE)
return null;
return rectRubber;
}
private void calcRectRubberBand() {
if (current.x < pressed.x) {
rectRubber.x = current.x;
rectRubber.width = pressed.x - current.x;
} else {
rectRubber.x = pressed.x;
rectRubber.width = current.x - pressed.x;
}
if (current.y < pressed.y) {
rectRubber.y = current.y;
rectRubber.height = pressed.y - current.y;
} else {
rectRubber.y = pressed.y;
rectRubber.height = current.y - pressed.y;
}
}
private void checkAction(int action, int x, int y, int deltaX, int deltaY,
long time, int mode) {
int mods = Binding.getModifiers(action);
if (Binding.getModifiers(action) != 0) {
int newAction = viewer.notifyMouseClicked(x, y, Binding.getMouseAction(
-pressedCount, mods), mode);
if (newAction == 0)
return;
if (newAction > 0)
action = newAction;
}
if (isRubberBandSelect(action)) {
calcRectRubberBand();
viewer.refresh(3, "rubberBand selection");
return;
}
if (checkUserAction(action, x, y, deltaX, deltaY, time, mode))
return;
if (viewer.getRotateBondIndex() >= 0) {
if (isBound(action, ACTION_rotateBranch)) {
viewer.moveSelected(deltaX, deltaY, x, y, null, false, true);
return;
}
if (!isBound(action, ACTION_rotate))
viewer.setRotateBondIndex(-1);
}
if (dragAtomIndex >= 0) {
switch (atomPickingMode) {
case PICKING_DRAG_ATOM:
case PICKING_DRAG_MINIMIZE:
case PICKING_DRAG_MOLECULE:
case PICKING_DRAG_MINIMIZE_MOLECULE:
if (dragGesture.getPointCount() == 1)
viewer.undoAction(dragAtomIndex, AtomCollection.TAINT_COORD, true);
checkMotion(Viewer.CURSOR_MOVE);
if (isBound(action, ACTION_rotateSelected)) {
BitSet bs = viewer.getAtomBits(Token.molecule, BitSetUtil.setBit(dragAtomIndex));
viewer.rotateSelected(getDegrees(deltaX, 0), getDegrees(deltaY, 1),
bs);
} else {
BitSet bs = null;
switch (atomPickingMode) {
case PICKING_DRAG_MOLECULE:
case PICKING_DRAG_MINIMIZE_MOLECULE:
bs = viewer.getAtomBits(Token.molecule, BitSetUtil.setBit(dragAtomIndex));
break;
}
viewer.moveAtomWithHydrogens(dragAtomIndex, deltaX, deltaY, bs);
}
// NAH! if (atomPickingMode == PICKING_DRAG_MINIMIZE_MOLECULE && (dragGesture.getPointCount() % 5 == 0))
// minimize(false);
return;
}
}
if (dragAtomIndex >= 0 && isBound(action, ACTION_assignNew)
&& atomPickingMode == PICKING_ASSIGN_ATOM) {
int nearestAtomIndex = viewer.findNearestAtomIndex(x, y, true);
if (nearestAtomIndex >= 0) {
if (measurementPending != null) {
measurementPending.setCount(1);
} else if (measuresEnabled) {
enterMeasurementMode();
}
addToMeasurement(nearestAtomIndex, null, true);
measurementPending.setColix(Graphics3D.MAGENTA);
} else if (measurementPending != null) {
measurementPending.setCount(1);
measurementPending.setColix(Graphics3D.GOLD);
}
if (measurementPending == null)
return;
measurementPending.traceX = x;
measurementPending.traceY = y;
viewer.refresh(3, "assignNew");
return;
}
if (!drawMode && !labelMode) {
if (isBound(action, ACTION_translate)) {
viewer.translateXYBy(deltaX, deltaY);
return;
}
if (isBound(action, ACTION_center)) {
if (pressedAtomIndex == Integer.MAX_VALUE)
pressedAtomIndex = viewer.findNearestAtomIndex(pressed.x, pressed.y);
Point3f pt = (pressedAtomIndex < 0 ? null : viewer
.getAtomPoint3f(pressedAtomIndex));
if (pt == null)
viewer.translateXYBy(deltaX, deltaY);
else
viewer.centerAt(x, y, pt);
return;
}
}
if (dragSelectedMode && haveSelection
&& (isBound(action, ACTION_dragSelected) || isBound(action, ACTION_rotateSelected))) {
int iatom = viewer.getSelectionSet(false).nextSetBit(0);
if (iatom < 0)
return;
if (dragGesture.getPointCount() == 1)
viewer.undoAction(iatom, AtomCollection.TAINT_COORD, true);
else
viewer.moveSelected(Integer.MAX_VALUE, 0, Integer.MIN_VALUE, Integer.MIN_VALUE, null, false, false);
checkMotion(Viewer.CURSOR_MOVE);
if (isBound(action, ACTION_rotateSelected) && viewer.allowRotateSelected())
viewer.rotateSelected(getDegrees(deltaX, 0), getDegrees(deltaY, 1),
null);
else
viewer.moveSelected(deltaX, deltaY, Integer.MIN_VALUE,
Integer.MIN_VALUE, null, true, false);
return;
}
if (drawMode
&& (isBound(action, ACTION_dragDrawObject) || isBound(action,
ACTION_dragDrawPoint)) || labelMode
&& isBound(action, ACTION_dragLabel)) {
checkMotion(Viewer.CURSOR_MOVE);
viewer.checkObjectDragged(dragged.x, dragged.y, x, y, action);
return;
}
if (checkMotionRotateZoom(action, x, deltaX, deltaY, true)) {
viewer.zoomBy(deltaY);
return;
}
if (isBound(action, ACTION_rotate)) {
float degX = getDegrees(deltaX, 0);
float degY = getDegrees(deltaY, 1);
if (viewer.useArcBall())
viewer.rotateArcBall(x, y, mouseDragFactor);
else
viewer.rotateXYBy(degX, degY);
return;
}
if (isBound(action, ACTION_rotateZorZoom)) {
if (Math.abs(deltaY) > 5 * Math.abs(deltaX)) {
// if (deltaY < 0 && deltaX > deltaY || deltaY > 0 && deltaX < deltaY)
checkMotion(Viewer.CURSOR_ZOOM);
viewer.zoomBy(deltaY);
} else if (Math.abs(deltaX) > 5 * Math.abs(deltaY)) {
// if (deltaX < 0 && deltaY > deltaX || deltaX > 0 && deltaY < deltaX)
checkMotion(Viewer.CURSOR_MOVE);
viewer.rotateZBy(-deltaX, Integer.MAX_VALUE, Integer.MAX_VALUE);
}
return;
} else if (isBound(action, ACTION_wheelZoom)) {
zoomByFactor(deltaY, Integer.MAX_VALUE, Integer.MAX_VALUE);
return;
} else if (isBound(action, ACTION_rotateZ)) {
checkMotion(Viewer.CURSOR_MOVE);
viewer.rotateZBy(-deltaX, Integer.MAX_VALUE, Integer.MAX_VALUE);
return;
}
if (viewer.getSlabEnabled()) {
if (isBound(action, ACTION_depth)) {
viewer.depthByPixels(deltaY);
return;
}
if (isBound(action, ACTION_slab)) {
viewer.slabByPixels(deltaY);
return;
}
if (isBound(action, ACTION_slabAndDepth)) {
viewer.slabDepthByPixels(deltaY);
return;
}
}
}
private float getDegrees(int delta, int i) {
int dim = (i == 0 ? viewer.getScreenWidth() : viewer.getScreenHeight());
dim = 400;
return ((float) delta) / dim * 180 * mouseDragFactor;
}
protected void zoomByFactor(int dz, int x, int y) {
if (dz == 0)
return;
checkMotion(Viewer.CURSOR_ZOOM);
viewer.zoomByFactor((float) Math.pow(mouseWheelFactor, dz), x, y);
viewer.setInMotion(false);
}
private boolean checkUserAction(int action, int x, int y,
int deltaX, int deltaY, long time, int mode) {
if (!binding.isUserAction(action))
return false;
Hashtable ht = binding.getBindings();
Enumeration e = ht.keys();
boolean ret = false;
Object obj;
while (e.hasMoreElements()) {
String key = (String) e.nextElement();
if (key.indexOf(action + "\t") != 0
|| !((obj = ht.get(key)) instanceof String[]))
continue;
String script = ((String[]) obj)[1];
script = TextFormat.simpleReplace(script,"_ACTION", "" + action);
script = TextFormat.simpleReplace(script,"_X", "" + x);
script = TextFormat.simpleReplace(script,"_Y", "" + (viewer.getScreenHeight() - y));
script = TextFormat.simpleReplace(script,"_DELTAX", "" + deltaX);
script = TextFormat.simpleReplace(script,"_DELTAY", "" + deltaY);
script = TextFormat.simpleReplace(script,"_TIME", "" + time);
script = TextFormat.simpleReplace(script,"_MODE", "" + mode);
viewer.evalStringQuiet(script);
ret = true;
}
return ret;
}
private boolean checkMotionRotateZoom(int action, int x,
int deltaX, int deltaY,
boolean inMotion) {
boolean isSlideZoom = isBound(action, ACTION_slideZoom);
boolean isRotateXY = isBound(action, ACTION_rotate);
boolean isRotateZorZoom = isBound(action, ACTION_rotateZorZoom);
if (!isSlideZoom && !isRotateXY && !isRotateZorZoom)
return false;
boolean isZoom = (isRotateZorZoom && (deltaX == 0 || Math.abs(deltaY) > 5 * Math.abs(deltaX)));
int cursor = (isZoom || isZoomArea(moved.x) || isBound(action, ACTION_wheelZoom) ? Viewer.CURSOR_ZOOM
: isRotateXY || isRotateZorZoom ? Viewer.CURSOR_MOVE : Viewer.CURSOR_DEFAULT);
if (viewer.getCursor() != Viewer.CURSOR_WAIT)
viewer.setCursor(cursor);
if (inMotion)
viewer.setInMotion(true);
return (isZoom || isSlideZoom && isZoomArea(pressed.x));
}
private boolean isZoomArea(int x) {
return x > viewer.getScreenWidth() * (viewer.isStereoDouble() ? 2 : 1)
* SLIDE_ZOOM_X_PERCENT / 100f;
}
private boolean checkPointOrAtomClicked(int x, int y, int mods,
int clickedCount, boolean atomOnly,
int mode) {
if (!viewer.haveModelSet())
return false;
// points are always picked up first, then atoms
// so that atom picking can be superceded by draw picking
int action = Binding.getMouseAction(clickedCount, mods);
if (action != Binding.MOVED) {
action = viewer.notifyMouseClicked(x, y, action, mode);
if (action == Binding.MOVED)
return false;
}
Point3fi nearestPoint = null;
int tokType = 0;
// t.tok will let us know if this is an atom or a bond that was clicked
if (!drawMode && !atomOnly) {
Token t = viewer.checkObjectClicked(x, y, action);
if (t != null) {
tokType = t.tok;
nearestPoint = (Point3fi) t.value;
}
}
if (tokType == Token.bonds)
clickedCount = 1;
if (nearestPoint != null && Float.isNaN(nearestPoint.x))
return false;
int nearestAtomIndex = (drawMode || nearestPoint != null ? -1 : viewer
.findNearestAtomIndex(x, y, true));
if (nearestAtomIndex >= 0
&& (clickedCount > 0 || measurementPending == null)
&& !viewer.isInSelectionSubset(nearestAtomIndex))
nearestAtomIndex = -1;
if (clickedCount == 0 && atomPickingMode != PICKING_ASSIGN_ATOM) {
// mouse move
if (measurementPending == null)
return (nearestAtomIndex >= 0);
if (nearestPoint != null
|| measurementPending.getIndexOf(nearestAtomIndex) == 0)
measurementPending.addPoint(nearestAtomIndex, nearestPoint, false);
if (measurementPending.haveModified())
viewer.setPendingMeasurement(measurementPending);
viewer.refresh(3, "measurementPending");
return (nearestAtomIndex >= 0);
}
setMouseMode();
if (isBound(action, ACTION_stopMotion)) {
viewer.stopMotion();
// continue checking --- no need to exit here
}
if (isBound(action, ACTION_clickFrank) && viewer.frankClicked(x, y)) {
viewer.popupMenu(-x, y, 'j');
return false;
}
if (isBound(action, ACTION_clickFrank) && viewer.frankClickedModelKit(x, y)) {
viewer.popupMenu(0, 0, 'm');
return false;
}
if (viewer.getNavigationMode() && atomPickingMode == PICKING_NAVIGATE
&& isBound(action, ACTION_pickNavigate)) {
viewer.navTranslatePercent(0f, x * 100f / viewer.getScreenWidth() - 50f,
y * 100f / viewer.getScreenHeight() - 50f);
return false;
}
// bond change by clicking on a bond
// bond deletion by clicking a bond
if (tokType == Token.bonds) {
if (isBound(action, bondPickingMode == PICKING_ROTATE_BOND
|| bondPickingMode == PICKING_ASSIGN_BOND ? ACTION_assignNew
: ACTION_deleteBond)) {
if (bondPickingMode == PICKING_ASSIGN_BOND)
viewer.undoAction(-1, Viewer.ACTION_SAVE, true);
switch (bondPickingMode) {
case PICKING_ASSIGN_BOND:
viewer.script("assign bond [{" + nearestPoint.index + "}] \""
+ pickBondAssignType + "\"");
break;
case PICKING_ROTATE_BOND:
viewer.setRotateBondIndex(nearestPoint.index);
break;
case PICKING_DELETE_BOND:
viewer.deleteBonds(BitSetUtil.setBit(nearestPoint.index));
}
return false;
}
} else if (tokType == Token.isosurface) {
return false;
} else {
if (atomPickingMode != PICKING_ASSIGN_ATOM && measurementPending != null
&& isBound(action, ACTION_pickMeasure)) {
atomOrPointPicked(nearestAtomIndex, nearestPoint, action);
if (addToMeasurement(nearestAtomIndex, nearestPoint, false) == 4)
toggleMeasurement();
return false;
}
if (isBound(action, ACTION_setMeasure)) {
if (measurementPending != null) {
addToMeasurement(nearestAtomIndex, nearestPoint, true);
toggleMeasurement();
} else if (!drawMode && !labelMode && !dragSelectedMode
&& measuresEnabled) {
enterMeasurementMode();
addToMeasurement(nearestAtomIndex, nearestPoint, true);
}
atomOrPointPicked(nearestAtomIndex, nearestPoint, action);
return false;
}
}
boolean isDragSelected = (dragSelectedMode && (isBound(action,
ACTION_rotateSelected) || isBound(action, ACTION_dragSelected)));
if (isDragSelected || isSelectAction(action)) {
// TODO: in drawMode the binding changes
if (tokType != Token.isosurface)
atomOrPointPicked(nearestAtomIndex, nearestPoint, isDragSelected ? 0 : action);
return (nearestAtomIndex >= 0);
}
if (isBound(action, ACTION_reset)) {
if (nearestAtomIndex < 0)
viewer.script("!reset");
return false;
}
return (nearestAtomIndex >= 0);
}
private boolean isSelectAction(int action) {
return (isBound(action, ACTION_pickAtom)
|| isBound(action, ACTION_pickPoint)
|| isBound(action, ACTION_selectToggle)
|| isBound(action, ACTION_selectAndNot)
|| isBound(action, ACTION_selectOr)
|| isBound(action, ACTION_selectToggleExtended)
|| isBound(action, ACTION_select));
}
protected void checkMotion(int cursor) {
if (viewer.getCursor() != Viewer.CURSOR_WAIT)
viewer.setCursor(cursor);
viewer.setInMotion(true);
}
private int addToMeasurement(int atomIndex, Point3fi nearestPoint,
boolean dblClick) {
if (atomIndex == -1 && nearestPoint == null) {
exitMeasurementMode();
return 0;
}
int measurementCount = measurementPending.getCount();
if (measurementPending.traceX != Integer.MIN_VALUE && measurementCount == 2)
measurementPending.setCount(measurementCount = 1);
return (measurementCount == 4 && !dblClick ? measurementCount
: measurementPending.addPoint(atomIndex, nearestPoint, true));
}
private void enterMeasurementMode() {
viewer.setCursor(Viewer.CURSOR_CROSSHAIR);
viewer.setPendingMeasurement(measurementPending = new MeasurementPending(
viewer.getModelSet()));
}
private void exitMeasurementMode() {
if (measurementPending == null)
return;
viewer.setPendingMeasurement(measurementPending = null);
viewer.setCursor(Viewer.CURSOR_DEFAULT);
}
private void toggleMeasurement() {
if (measurementPending == null)
return;
int measurementCount = measurementPending.getCount();
if (measurementCount >= 2 && measurementCount <= 4)
viewer.script("!measure " + measurementPending.getMeasurementScript(" ", true));
exitMeasurementMode();
}
Hashtable timeouts;
public String showTimeout(String name) {
StringBuffer sb = new StringBuffer();
if (timeouts != null) {
Enumeration e = timeouts.elements();
while (e.hasMoreElements()) {
TimeoutThread t = (TimeoutThread) e.nextElement();
if (name == null || t.name.equalsIgnoreCase(name))
sb.append(t.toString()).append("\n");
}
}
return (sb.length() > 0 ? sb.toString() : "<no timeouts set>");
}
public void clearTimeouts() {
if (timeouts == null)
return;
Enumeration e = timeouts.elements();
while (e.hasMoreElements())
((Thread) e.nextElement()).interrupt();
timeouts.clear();
}
public void setTimeout(String name, int mSec, String script) {
if (name == null) {
clearTimeouts();
return;
}
if (timeouts == null)
timeouts = new Hashtable();
if (mSec == 0) {
Thread t = (Thread) timeouts.get(name);
if (t != null) {
t.interrupt();
timeouts.remove(name);
}
return;
}
TimeoutThread t = (TimeoutThread) timeouts.get(name);
if (t != null) {
t.set(mSec, script);
return;
}
t = new TimeoutThread(name, mSec, script);
timeouts.put(name, t);
t.start();
}
private class TimeoutThread extends Thread {
String name;
private int ms;
private long targetTime;
private int status;
private String script;
TimeoutThread(String name, int ms, String script) {
this.name = name;
this.ms = ms;
this.script = script;
targetTime = System.currentTimeMillis() + Math.abs(ms);
}
void set(int ms, String script) {
this.ms = ms;
if (script != null && script.length() != 0)
this.script = script;
}
public String toString() {
return "timeout name=" + name + " executions=" + status + " mSec=" + ms
+ " secRemaining=" + (targetTime - System.currentTimeMillis())/1000f + " script=" + script + " thread=" + Thread.currentThread().getName();
}
public void run() {
if (script == null || script.length() == 0 || ms == 0)
return;
//System.out.println("I am the timeout thread, and my name is " + Thread.currentThread().getName());
Thread.currentThread().setName("timeout " + name);
//if (true || Logger.debugging)
//Logger.info(toString());
Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
try {
while (true) {
Thread.sleep(26);
if (targetTime > System.currentTimeMillis())
continue;
status++;
targetTime += Math.abs(ms);
if (timeouts.get(name) == null)
break;
if (ms > 0)
timeouts.remove(name);
//System.out.println("I'm going to execute " + script + " now");
//if (Logger.debugging)
//viewer.script(script);
//else
viewer.evalStringQuiet(script);
if (ms > 0)
break;
}
} catch (InterruptedException ie) {
Logger.info("Timeout " + name + " interrupted");
} catch (Exception ie) {
Logger.info("Timeout " + name + " Exception: " + ie);
}
//System.out.println("I'm done");
timeouts.remove(name);
}
}
public void hoverOn(int atomIndex) {
viewer.hoverOn(atomIndex, Binding.getMouseAction(clickedCount, moved.modifiers));
}
public void hoverOff() {
try {
viewer.hoverOff();
} catch (Exception e) {
// ignore
}
}
class HoverWatcher implements Runnable {
public void run() {
Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
int hoverDelay;
try {
while (Thread.currentThread().equals(hoverWatcherThread) && (hoverDelay = viewer.getHoverDelay()) > 0) {
Thread.sleep(hoverDelay);
if (current.x == moved.x && current.y == moved.y
&& current.time == moved.time) { // the last event was mouse
// move
long currentTime = System.currentTimeMillis();
int howLong = (int) (currentTime - moved.time);
if (howLong > hoverDelay) {
if (Thread.currentThread().equals(hoverWatcherThread) && !viewer.getInMotion()
&& !viewer.getSpinOn() && !viewer.getNavOn()
&& !viewer.checkObjectHovered(current.x, current.y)) {
int atomIndex = viewer.findNearestAtomIndex(current.x, current.y);
if (atomIndex >= 0) {
hoverOn(atomIndex);
}
}
}
}
}
} catch (InterruptedException ie) {
Logger.debug("Hover interrupted");
} catch (Exception ie) {
Logger.debug("Hover Exception: " + ie);
}
}
}
//////////////// picking ///////////////////
private MeasurementPending measurementQueued;
private void resetMeasurement() {
// doesn't reset the measurement that is being picked using
// double-click, just the one using set picking measure.
measurementQueued = new MeasurementPending(viewer.getModelSet());
}
private int pickingStyle;
private int atomPickingMode = PICKING_IDENTIFY;
private int pickingStyleSelect = PICKINGSTYLE_SELECT_JMOL;
private int pickingStyleMeasure = PICKINGSTYLE_MEASURE_OFF;
private int rootPickingStyle = PICKINGSTYLE_SELECT_JMOL;
private String pickAtomAssignType = "C";
private char pickBondAssignType = 'p';
private int bondPickingMode;
private boolean isPickAtomAssignCharge;
public String getPickingState() {
// the pickingMode is not reported in the state. But when we do an UNDO,
// we want to restore this.
String script = ";set modelkitMode " + viewer.getModelkitMode()
+ ";set picking " + getPickingModeName(atomPickingMode);
if (atomPickingMode == PICKING_ASSIGN_ATOM)
script += "_" + pickAtomAssignType;
script += ";";
if (bondPickingMode != PICKING_OFF)
script += "set picking " + getPickingModeName(bondPickingMode);
if (bondPickingMode == PICKING_ASSIGN_BOND)
script += "_" + pickBondAssignType;
script += ";";
return script;
}
public int getAtomPickingMode() {
return atomPickingMode;
}
public int getBondPickingMode() {
return bondPickingMode;
}
public void setPickingMode(int pickingMode) {
switch (pickingMode) {
case -1: // from set modelkit OFF
bondPickingMode = PICKING_IDENTIFY_BOND;
pickingMode = PICKING_IDENTIFY;
break;
case PICKING_IDENTIFY_BOND:
case PICKING_ROTATE_BOND:
case PICKING_ASSIGN_BOND:
viewer.setBooleanProperty("bondPicking", true);
bondPickingMode = pickingMode;
return;
case PICKING_DELETE_BOND:
bondPickingMode = pickingMode;
if (viewer.getBondPicking())
return;
break;
// if we have bondPicking mode, then we don't set atomPickingMode to this
}
atomPickingMode = pickingMode;
resetMeasurement();
}
void setAtomPickingOption(String option) {
switch (atomPickingMode) {
case PICKING_ASSIGN_ATOM:
pickAtomAssignType = option;
isPickAtomAssignCharge = (pickAtomAssignType.equals("Pl") || pickAtomAssignType.equals("Mi"));
break;
}
}
void setBondPickingOption(String option) {
switch (bondPickingMode) {
case PICKING_ASSIGN_BOND:
pickBondAssignType = Character.toLowerCase(option.charAt(0));
break;
}
}
public int getPickingStyle() {
return pickingStyle;
}
public void setPickingStyle(int pickingStyle) {
this.pickingStyle = pickingStyle;
if (pickingStyle >= PICKINGSTYLE_MEASURE_ON) {
pickingStyleMeasure = pickingStyle;
resetMeasurement();
} else {
if (pickingStyle < PICKINGSTYLE_SELECT_DRAG)
rootPickingStyle = pickingStyle;
pickingStyleSelect = pickingStyle;
}
rubberbandSelectionMode = false;
switch (pickingStyleSelect) {
case PICKINGSTYLE_SELECT_PFAAT:
if (binding.getName() != "extendedSelect")
setBinding(pfaatBinding = (pfaatBinding == null ? new PfaatBinding() : pfaatBinding));
break;
case PICKINGSTYLE_SELECT_DRAG:
if (binding.getName() != "drag")
setBinding(dragBinding = (dragBinding == null ? new DragBinding() : dragBinding));
rubberbandSelectionMode = true;
break;
case PICKINGSTYLE_SELECT_RASMOL:
if (binding.getName() != "selectOrToggle")
setBinding(rasmolBinding = (rasmolBinding == null ? new RasmolBinding() : rasmolBinding));
break;
default:
if (binding != jmolBinding)
setBinding(jmolBinding);
}
if (binding.getName() != "drag")
predragBinding = binding;
}
protected void setBinding(Binding newBinding) {
binding = newBinding;
}
private void atomOrPointPicked(int atomIndex, Point3fi ptClicked, int action) {
// atomIndex < 0 is off structure.
// if picking spin or picking symmetry is on, then
// we need to enter this method to process those events.
if (atomIndex < 0) {
resetMeasurement(); // for set picking measure only
if (isBound(action, ACTION_selectNone)) {
viewer.script("select none");
return;
}
if (atomPickingMode != PICKING_SPIN
&& atomPickingMode != PICKING_SYMMETRY)
return;
}
int n = 2;
switch (atomPickingMode) {
case PICKING_DRAG_ATOM:
// this is done in mouse drag, not mouse release
case PICKING_DRAG_MINIMIZE:
return;
case PICKING_OFF:
return;
case PICKING_STRUTS:
case PICKING_CONNECT:
case PICKING_DELETE_BOND:
boolean isDelete = (atomPickingMode == PICKING_DELETE_BOND);
boolean isStruts = (atomPickingMode == PICKING_STRUTS);
if (!isBound(action, (isDelete ? ACTION_deleteBond : ACTION_connectAtoms)))
return;
if (measurementQueued == null || measurementQueued.getCount() >= 2)
resetMeasurement();
if (queueAtom(atomIndex, ptClicked) != 2)
return;
String cAction = (isDelete
|| measurementQueued.isConnected(viewer.getModelSet().atoms, 2) ? " DELETE"
: isStruts ? "STRUTS" : "");
viewer.script("connect "
+ measurementQueued.getMeasurementScript(" ", true) + cAction);
return;
case PICKING_MEASURE_TORSION:
n++;
// fall through
case PICKING_MEASURE_ANGLE:
n++;
// fall through
case PICKING_MEASURE:
case PICKING_MEASURE_DISTANCE:
case PICKING_MEASURE_SEQUENCE:
if (!isBound(action, ACTION_pickMeasure))
return;
if (measurementQueued == null || measurementQueued.getCount() >= n)
resetMeasurement();
if (queueAtom(atomIndex, ptClicked) < n)
return;
if (atomPickingMode == PICKING_MEASURE_SEQUENCE) {
getSequence();
return;
}
viewer.setStatusMeasuring("measurePicked", n, measurementQueued
.getStringDetail(), measurementQueued.getValue());
if (atomPickingMode == PICKING_MEASURE
|| pickingStyleMeasure == PICKINGSTYLE_MEASURE_ON) {
viewer.script("measure "
+ measurementQueued.getMeasurementScript(" ", true));
}
return;
}
int mode = (measurementPending != null && atomPickingMode != PICKING_IDENTIFY ? PICKING_IDENTIFY : atomPickingMode);
switch (mode) {
case PICKING_CENTER:
if (!isBound(action, ACTION_pickAtom))
return;
if (ptClicked == null) {
viewer.script("zoomTo (atomindex=" + atomIndex + ")");
viewer.setStatusAtomPicked(atomIndex, null);
} else {
viewer.script("zoomTo " + Escape.escape(ptClicked));
}
return;
case PICKING_SPIN:
case PICKING_SYMMETRY:
checkTwoAtomAction(action, ptClicked, atomIndex);
}
if (ptClicked != null)
return;
// atoms only here:
BitSet bs;
String spec = "atomindex=" + atomIndex;
switch (mode) {
case PICKING_IDENTIFY:
if (isBound(action, ACTION_pickAtom))
viewer.setStatusAtomPicked(atomIndex, null);
return;
case PICKING_LABEL:
if (isBound(action, ACTION_pickLabel)) {
viewer.script("set labeltoggle {atomindex=" + atomIndex + "}");
viewer.setStatusAtomPicked(atomIndex, null);
}
return;
case PICKING_INVERT_STEREO:
if (isBound(action, ACTION_assignNew)) {
bs = viewer.getAtomBitSet("connected(atomIndex=" + atomIndex
+ ") and !within(SMARTS,'[r50,R]')");
int nb = bs.cardinality();
switch (nb) {
case 0:
case 1:
// not enough non-ring atoms
return;
case 2:
break;
case 3:
case 4:
// three or four are not in a ring. So let's find the shortest two
// branches and invert them.
int[] lengths = new int[nb];
int[] points = new int[nb];
int ni = 0;
for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i + 1), ni++) {
lengths[ni] = viewer.getBranchBitSet(i, atomIndex).cardinality();
points[ni] = i;
}
for (int j = 0; j < nb - 2; j++) {
int max = Integer.MIN_VALUE;
int imax = 0;
for (int i = 0; i < nb; i++)
if (lengths[i] >= max && bs.get(points[i])) {
imax = points[i];
max = lengths[i];
}
bs.clear(imax);
}
}
viewer.undoAction(atomIndex, AtomCollection.TAINT_COORD, true);
viewer.invertSelected(null, null, atomIndex, bs);
viewer.setStatusAtomPicked(atomIndex, "inverted: " + Escape.escape(bs));
}
return;
case PICKING_DELETE_ATOM:
if (isBound(action, ACTION_deleteAtom)) {
bs = getSelectionSet("(" + spec + ")");
viewer.deleteAtoms(bs, false);
viewer.setStatusAtomPicked(atomIndex, "deleted: " + Escape.escape(bs));
}
return;
}
// set picking select options:
switch (atomPickingMode) {
default:
return;
case PICKING_SELECT_ATOM:
applySelectStyle(spec, action);
break;
case PICKING_SELECT_GROUP:
applySelectStyle("within(group, " + spec + ")", action);
break;
case PICKING_SELECT_CHAIN:
applySelectStyle("within(chain, " + spec + ")", action);
break;
case PICKING_SELECT_POLYMER:
applySelectStyle("within(polymer, " + spec + ")", action);
break;
case PICKING_SELECT_STRUCTURE:
applySelectStyle("within(structure, " + spec + ")", action);
break;
case PICKING_SELECT_MOLECULE:
applySelectStyle("within(molecule, " + spec + ")", action);
break;
case PICKING_SELECT_MODEL:
applySelectStyle("within(model, " + spec + ")", action);
break;
// only the next two use VISIBLE (as per the documentation)
case PICKING_SELECT_ELEMENT:
applySelectStyle("visible and within(element, " + spec + ")", action);
break;
case PICKING_SELECT_SITE:
applySelectStyle("visible and within(site, " + spec + ")", action);
break;
}
viewer.clearClickCount();
viewer.setStatusAtomPicked(atomIndex, null);
}
private void getSequence() {
int a1 = measurementQueued.getAtomIndex(1);
int a2 = measurementQueued.getAtomIndex(2);
if (a1 < 0 || a2 < 0)
return;
String sequence = viewer.getSmiles(a1, a2, null, true, false, false, false);
viewer.setStatusMeasuring("measureSequence", -2, sequence, 0);
}
private void checkTwoAtomAction(int action, Point3fi ptClicked, int atomIndex) {
if (!isBound(action, ACTION_pickAtom))
return;
boolean isSpin = (atomPickingMode == PICKING_SPIN);
if (viewer.getSpinOn() || viewer.getNavOn() || viewer.getPendingMeasurement() != null) {
resetMeasurement();
viewer.script("spin off");
return;
}
if (measurementQueued.getCount() >= 2)
resetMeasurement();
int queuedAtomCount = measurementQueued.getCount();
if (queuedAtomCount == 1) {
if (ptClicked == null) {
if (measurementQueued.getAtomIndex(1) == atomIndex)
return;
} else {
if (measurementQueued.getAtom(1).distance(ptClicked) == 0)
return;
}
}
if (atomIndex >= 0 || ptClicked != null)
queuedAtomCount = queueAtom(atomIndex, ptClicked);
if (queuedAtomCount < 2) {
if (isSpin)
viewer.scriptStatus(queuedAtomCount == 1 ?
GT._("pick one more atom in order to spin the model around an axis") :
GT._("pick two atoms in order to spin the model around an axis"));
else
viewer.scriptStatus(queuedAtomCount == 1 ?
GT._("pick one more atom in order to display the symmetry relationship") :
GT._("pick two atoms in order to display the symmetry relationship between them"));
return;
}
String s = measurementQueued.getMeasurementScript(" ", false);
if (isSpin)
viewer.script("spin" + s + " " + viewer.getPickingSpinRate());
else
viewer.script("draw symop" + s + ";show symop" + s);
}
private int queueAtom(int atomIndex, Point3fi ptClicked) {
int n = measurementQueued.addPoint(atomIndex, ptClicked, true);
if (atomIndex >= 0)
viewer.setStatusAtomPicked(atomIndex, "Atom #" + n + ":"
+ viewer.getAtomInfo(atomIndex));
return n;
}
private boolean selectionWorking = false;
private ScriptEvaluator eval;
private void applySelectStyle(String item, int action) {
if (measurementPending != null || selectionWorking)
return;
selectionWorking = true;
String s = (rubberbandSelectionMode || isBound(action,
ACTION_selectToggle) ? "selected and not (" + item
+ ") or (not selected) and "
: isBound(action, ACTION_selectAndNot) ? "selected and not "
: isBound(action, ACTION_selectOr) ? "selected or "
: action == 0 || isBound(action, ACTION_selectToggleExtended) ? "selected tog "
: isBound(action, ACTION_select) ? "" : null);
if (s != null) {
s += "(" + item + ")";
if (Logger.debugging)
Logger.debug(s);
BitSet bs = getSelectionSet(s);
if (bs != null) {
viewer.select(bs, false);
viewer.refresh(3, "selections set");
}
}
selectionWorking = false;
}
private BitSet getSelectionSet(String script) {
try {
if (eval == null)
eval = new ScriptEvaluator(viewer);
return viewer.getAtomBitSet(eval, script);
} catch (Exception e) {
// ignore
}
return null;
}
protected class MotionPoint {
int index;
int x;
int y;
long time;
void set(int index, int x, int y, long time) {
this.index = index;
this.x = x;
this.y = y;
this.time = time;
}
public String toString() {
return "[x = " + x + " y = " + y + " time = " + time + " ]";
}
}
protected Gesture dragGesture = new Gesture(20);
protected class Gesture {
private int action;
MotionPoint[] nodes;
private int ptNext;
private long time0;
Gesture(int nPoints) {
nodes = new MotionPoint[nPoints];
for (int i = 0; i < nPoints; i++)
nodes[i] = new MotionPoint();
}
void setAction(int action, long time) {
this.action = action;
ptNext = 0;
time0 = time;
for (int i = 0; i < nodes.length; i++)
nodes[i].index = -1;
}
int getAction() {
return action;
}
int add(int action, int x, int y, long time) {
this.action = action;
getNode(ptNext).set(ptNext, x, y, time - time0);
ptNext++;
return ptNext;
}
public long getTimeDifference(int nPoints) {
nPoints = getPointCount(nPoints, 0);
if (nPoints < 2)
return 0;
MotionPoint mp1 = getNode(ptNext - 1);
MotionPoint mp0 = getNode(ptNext - nPoints);
return mp1.time - mp0.time;
}
public float getSpeedPixelsPerMillisecond(int nPoints, int nPointsPrevious) {
nPoints = getPointCount(nPoints, nPointsPrevious);
if (nPoints < 2)
return 0;
MotionPoint mp1 = getNode(ptNext - 1 - nPointsPrevious);
MotionPoint mp0 = getNode(ptNext - nPoints - nPointsPrevious);
float dx = ((float) (mp1.x - mp0.x)) / viewer.getScreenWidth() * 360;
float dy = ((float) (mp1.y - mp0.y)) / viewer.getScreenHeight() * 360;
return (float) Math.sqrt(dx * dx + dy * dy) / (mp1.time - mp0.time);
}
int getDX(int nPoints, int nPointsPrevious) {
nPoints = getPointCount(nPoints, nPointsPrevious);
if (nPoints < 2)
return 0;
MotionPoint mp1 = getNode(ptNext - 1 - nPointsPrevious);
MotionPoint mp0 = getNode(ptNext - nPoints - nPointsPrevious);
return mp1.x - mp0.x;
}
int getDY(int nPoints, int nPointsPrevious) {
nPoints = getPointCount(nPoints, nPointsPrevious);
if (nPoints < 2)
return 0;
MotionPoint mp1 = getNode(ptNext - 1 - nPointsPrevious);
MotionPoint mp0 = getNode(ptNext - nPoints - nPointsPrevious);
return mp1.y - mp0.y;
}
int getPointCount() {
return ptNext;
}
int getPointCount(int nPoints, int nPointsPrevious) {
if (nPoints > nodes.length - nPointsPrevious)
nPoints = nodes.length - nPointsPrevious;
int n = nPoints + 1;
for (; --n >= 0; )
if (getNode(ptNext - n - nPointsPrevious).index >= 0)
break;
return n;
}
MotionPoint getNode(int i) {
return nodes[(i + nodes.length + nodes.length) % nodes.length];
}
public String toString() {
if (nodes.length == 0) return "" + this;
return Binding.getMouseActionName(action, false) + " nPoints = " + ptNext + " " + nodes[0];
}
}
}