1 /* $RCSfile$
2  * $Author: hansonr $
3  * $Date: 2009-07-31 09:22:19 -0500 (Fri, 31 Jul 2009) $
4  * $Revision: 11291 $
5  *
6  * Copyright (C) 2002-2005  The Jmol Development Team
7  *
8  * Contact: jmol-developers@lists.sf.net
9  *
10  *  This library is free software; you can redistribute it and/or
11  *  modify it under the terms of the GNU Lesser General Public
12  *  License as published by the Free Software Foundation; either
13  *  version 2.1 of the License, or (at your option) any later version.
14  *
15  *  This library is distributed in the hope that it will be useful,
16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  *  Lesser General Public License for more details.
19  *
20  *  You should have received a copy of the GNU Lesser General Public
21  *  License along with this library; if not, write to the Free Software
22  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
23  */
24 package org.jmol.viewer;
25 
26 import java.util.Map;
27 
28 import org.jmol.api.EventManager;
29 import org.jmol.api.GenericPlatform;
30 import org.jmol.api.Interface;
31 import org.jmol.awtjs.Event;
32 import org.jmol.i18n.GT;
33 
34 import javajs.util.AU;
35 import javajs.util.BS;
36 import org.jmol.modelset.AtomCollection;
37 import org.jmol.modelset.MeasurementPending;
38 import org.jmol.script.SV;
39 import org.jmol.script.ScriptEval;
40 import org.jmol.script.T;
41 import org.jmol.thread.HoverWatcherThread;
42 import org.jmol.util.BSUtil;
43 import org.jmol.util.C;
44 import org.jmol.util.Escape;
45 import org.jmol.util.Logger;
46 import org.jmol.util.Point3fi;
47 
48 import javajs.util.P3;
49 import javajs.util.PT;
50 
51 import org.jmol.util.Rectangle;
52 import org.jmol.viewer.binding.Binding;
53 import org.jmol.viewer.binding.JmolBinding;
54 
55 public class ActionManager implements EventManager {
56 
57   protected Viewer vwr;
58   protected boolean haveMultiTouchInput;
59   protected boolean isMultiTouch;
60 
61   public Binding b;
62 
63   private Binding jmolBinding;
64   private Binding pfaatBinding;
65   private Binding dragBinding;
66   private Binding rasmolBinding;
67   private Binding predragBinding;
68   private int LEFT_CLICKED;
69   private int LEFT_DRAGGED;
70 
71   /**
72    *
73    * @param vwr
74    * @param commandOptions
75    */
setViewer(Viewer vwr, String commandOptions)76   public void setViewer(Viewer vwr, String commandOptions) {
77     this.vwr = vwr;
78     if (!Viewer.isJS)
79       createActions();
80     setBinding(jmolBinding = new JmolBinding());
81     LEFT_CLICKED = Binding.getMouseAction(1, Binding.LEFT, Event.CLICKED);
82     LEFT_DRAGGED = Binding.getMouseAction(1, Binding.LEFT, Event.DRAGGED);
83     dragGesture = new Gesture(20, vwr);
84   }
85 
86   protected Thread hoverWatcherThread;
87 
checkHover()88   public void checkHover() {
89     if (zoomTrigger) {
90       zoomTrigger = false;
91       if (vwr.currentCursor == GenericPlatform.CURSOR_ZOOM)
92         vwr.setCursor(GenericPlatform.CURSOR_DEFAULT);
93       vwr.setInMotion(false);
94       return;
95     }
96     if (!vwr.getInMotion(true) && !vwr.tm.spinOn && !vwr.tm.navOn
97         && !vwr.checkObjectHovered(current.x, current.y)) {
98       int atomIndex = vwr.findNearestAtomIndex(current.x, current.y);
99       if (atomIndex < 0)
100         return;
101       boolean isLabel = (apm == PICKING_LABEL && bnd(
102           Binding
103               .getMouseAction(clickedCount, moved.modifiers, Event.DRAGGED),
104           ACTION_dragLabel));
105       vwr.hoverOn(atomIndex, isLabel);
106     }
107   }
108 
109   /**
110    *
111    * Specific to ActionManagerMT -- for processing SparshUI gestures
112    *
113    * @param groupID
114    * @param eventType
115    * @param touchID
116    * @param iData
117    * @param pt
118    * @param time
119    */
processMultitouchEvent(int groupID, int eventType, int touchID, int iData, P3 pt, long time)120   public void processMultitouchEvent(int groupID, int eventType, int touchID, int iData,
121                            P3 pt, long time) {
122     // see subclass
123   }
124 
125   /**
126    *
127    * @param desc
128    * @param name
129    */
bind(String desc, String name)130   void bind(String desc, String name) {
131     int jmolAction = getActionFromName(name);
132     int mouseAction = Binding.getMouseActionStr(desc);
133     if (mouseAction == 0)
134       return;
135     if (jmolAction >= 0) {
136       b.bindAction(mouseAction, jmolAction);
137     } else {
138       b.bindName(mouseAction, name);
139     }
140   }
141 
clearBindings()142   protected void clearBindings() {
143     setBinding(jmolBinding = new JmolBinding());
144     pfaatBinding = null;
145     dragBinding = null;
146     rasmolBinding = null;
147   }
148 
unbindAction(String desc, String name)149   void unbindAction(String desc, String name) {
150     if (desc == null && name == null) {
151       clearBindings();
152       return;
153     }
154     int jmolAction = getActionFromName(name);
155     int mouseAction = Binding.getMouseActionStr(desc);
156     if (jmolAction >= 0)
157       b.unbindAction(mouseAction, jmolAction);
158     else if (mouseAction != 0)
159       b.unbindName(mouseAction, name);
160     if (name == null)
161       b.unbindUserAction(desc);
162   }
163 
164   //// Gestures
165 
166   private Gesture dragGesture;
167 
168   /*
169    * a "Jmol action" is one of these:
170    *
171    * A Jmol action is "bound" to a mouse action by the
172    * simple act of concatenating string "jmol action" + \t + "mouse action"
173    *
174    *
175    */
176   public final static int ACTION_assignNew = 0;
177   public final static int ACTION_center = 1;
178   public final static int ACTION_clickFrank = 2;
179   public final static int ACTION_connectAtoms = 3;
180   public final static int ACTION_deleteAtom = 4;
181   public final static int ACTION_deleteBond = 5;
182   public final static int ACTION_depth = 6;
183   public final static int ACTION_dragAtom = 7;
184   public final static int ACTION_dragDrawObject = 8;
185   public final static int ACTION_dragDrawPoint = 9;
186   public final static int ACTION_dragLabel = 10;
187   public final static int ACTION_dragMinimize = 11;
188   public final static int ACTION_dragMinimizeMolecule = 12;
189   public final static int ACTION_dragSelected = 13;
190   public final static int ACTION_dragZ = 14;
191   public final static int ACTION_multiTouchSimulation = 15;
192   public final static int ACTION_navTranslate = 16;
193   public final static int ACTION_pickAtom = 17;
194   public final static int ACTION_pickIsosurface = 18;
195   public final static int ACTION_pickLabel = 19;
196   public final static int ACTION_pickMeasure = 20;
197   public final static int ACTION_pickNavigate = 21;
198   public final static int ACTION_pickPoint = 22;
199   public final static int ACTION_popupMenu = 23;
200   public final static int ACTION_reset = 24;
201   public final static int ACTION_rotate = 25;
202   public final static int ACTION_rotateBranch = 26;
203   public final static int ACTION_rotateSelected = 27;
204   public final static int ACTION_rotateZ = 28;
205   public final static int ACTION_rotateZorZoom = 29;
206   public final static int ACTION_select = 30;
207   public final static int ACTION_selectAndDrag = 31;
208   public final static int ACTION_selectAndNot = 32;
209   public final static int ACTION_selectNone = 33;
210   public final static int ACTION_selectOr = 34;
211   public final static int ACTION_selectToggle = 35;
212   public final static int ACTION_selectToggleExtended = 36;
213   public final static int ACTION_setMeasure = 37;
214   public final static int ACTION_slab = 38;
215   public final static int ACTION_slabAndDepth = 39;
216   public final static int ACTION_slideZoom = 40;
217   public final static int ACTION_spinDrawObjectCCW = 41;
218   public final static int ACTION_spinDrawObjectCW = 42;
219   public final static int ACTION_stopMotion = 43;
220   public final static int ACTION_swipe = 44;
221   public final static int ACTION_translate = 45;
222   public final static int ACTION_wheelZoom = 46;
223   public final static int ACTION_count = 47;
224 
225   final static String[] actionInfo = new String[ACTION_count];
226   final static String[] actionNames = new String[ACTION_count];
227 
newAction(int i, String name, String info)228   static void newAction(int i, String name, String info) {
229     actionInfo[i] = info;
230     actionNames[i] = name;
231   }
232 
createActions()233   void createActions() {
234     if (actionInfo[ACTION_assignNew] != null)
235       return;
236     // OK for J2S because actionInfo and actionNames are both private
237     newAction(ACTION_assignNew, "_assignNew", GT.o(GT.$(
238         "assign/new atom or bond (requires {0})"),
239         "set picking assignAtom_??/assignBond_?"));
240     newAction(ACTION_center, "_center", GT.$("center"));
241     newAction(ACTION_clickFrank, "_clickFrank", GT
242         .$("pop up recent context menu (click on Jmol frank)"));
243     newAction(ACTION_deleteAtom, "_deleteAtom", GT.o(GT.$(
244         "delete atom (requires {0})"), "set picking DELETE ATOM"));
245     newAction(ACTION_deleteBond, "_deleteBond", GT.o(GT.$(
246         "delete bond (requires {0})"), "set picking DELETE BOND"));
247     newAction(ACTION_depth, "_depth", GT.o(GT.$(
248         "adjust depth (back plane; requires {0})"), "SLAB ON"));
249     newAction(ACTION_dragAtom, "_dragAtom", GT.o(GT.$("move atom (requires {0})"),
250         "set picking DRAGATOM"));
251     newAction(ACTION_dragDrawObject, "_dragDrawObject", GT.o(GT.$(
252         "move whole DRAW object (requires {0})"), "set picking DRAW"));
253     newAction(ACTION_dragDrawPoint, "_dragDrawPoint", GT.o(GT.$(
254         "move specific DRAW point (requires {0})"), "set picking DRAW"));
255     newAction(ACTION_dragLabel, "_dragLabel", GT.o(GT.$("move label (requires {0})"),
256         "set picking LABEL"));
257     newAction(ACTION_dragMinimize, "_dragMinimize", GT.o(GT.$(
258         "move atom and minimize molecule (requires {0})"),
259         "set picking DRAGMINIMIZE"));
260     newAction(ACTION_dragMinimizeMolecule, "_dragMinimizeMolecule", GT.o(GT.$(
261         "move and minimize molecule (requires {0})"),
262         "set picking DRAGMINIMIZEMOLECULE"));
263     newAction(ACTION_dragSelected, "_dragSelected", GT.o(GT.$(
264         "move selected atoms (requires {0})"), "set DRAGSELECTED"));
265     newAction(ACTION_dragZ, "_dragZ", GT.o(GT.$(
266         "drag atoms in Z direction (requires {0})"), "set DRAGSELECTED"));
267     newAction(ACTION_multiTouchSimulation, "_multiTouchSimulation", GT
268         .$("simulate multi-touch using the mouse)"));
269     newAction(ACTION_navTranslate, "_navTranslate", GT.o(GT.$(
270         "translate navigation point (requires {0} and {1})"), new String[] {
271             "set NAVIGATIONMODE", "set picking NAVIGATE" }));
272     newAction(ACTION_pickAtom, "_pickAtom", GT.$("pick an atom"));
273     newAction(ACTION_connectAtoms, "_pickConnect", GT.o(GT.$(
274         "connect atoms (requires {0})"), "set picking CONNECT"));
275     newAction(ACTION_pickIsosurface, "_pickIsosurface", GT.o(GT.$(
276         "pick an ISOSURFACE point (requires {0}"), "set DRAWPICKING"));
277     newAction(ACTION_pickLabel, "_pickLabel", GT.o(GT.$(
278         "pick a label to toggle it hidden/displayed (requires {0})"),
279         "set picking LABEL"));
280     newAction(
281         ACTION_pickMeasure,
282         "_pickMeasure",
283         GT.o(GT
284             .$(
285                 "pick an atom to include it in a measurement (after starting a measurement or after {0})"),
286                 "set picking DISTANCE/ANGLE/TORSION"));
287     newAction(ACTION_pickNavigate, "_pickNavigate", GT.o(GT.$(
288         "pick a point or atom to navigate to (requires {0})"),
289         "set NAVIGATIONMODE"));
290     newAction(ACTION_pickPoint, "_pickPoint", GT.o(GT
291         .$("pick a DRAW point (for measurements) (requires {0}"),
292             "set DRAWPICKING"));
293     newAction(ACTION_popupMenu, "_popupMenu", GT
294         .$("pop up the full context menu"));
295     newAction(ACTION_reset, "_reset", GT
296         .$("reset (when clicked off the model)"));
297     newAction(ACTION_rotate, "_rotate", GT.$("rotate"));
298     newAction(ACTION_rotateBranch, "_rotateBranch", GT.o(GT.$(
299         "rotate branch around bond (requires {0})"), "set picking ROTATEBOND"));
300     newAction(ACTION_rotateSelected, "_rotateSelected", GT.o(GT.$(
301         "rotate selected atoms (requires {0})"), "set DRAGSELECTED"));
302     newAction(ACTION_rotateZ, "_rotateZ", GT.$("rotate Z"));
303     newAction(
304         ACTION_rotateZorZoom,
305         "_rotateZorZoom",
306         GT
307             .$("rotate Z (horizontal motion of mouse) or zoom (vertical motion of mouse)"));
308     newAction(ACTION_select, "_select", GT.o(GT.$("select an atom (requires {0})"),
309         "set pickingStyle EXTENDEDSELECT"));
310     newAction(ACTION_selectAndDrag, "_selectAndDrag", GT.o(GT.$(
311         "select and drag atoms (requires {0})"), "set DRAGSELECTED"));
312     newAction(ACTION_selectAndNot, "_selectAndNot", GT.o(GT.$(
313         "unselect this group of atoms (requires {0})"),
314         "set pickingStyle DRAG/EXTENDEDSELECT"));
315     newAction(ACTION_selectNone, "_selectNone", GT.o(GT.$(
316         "select NONE (requires {0})"), "set pickingStyle EXTENDEDSELECT"));
317     newAction(ACTION_selectOr, "_selectOr", GT.o(GT.$(
318         "add this group of atoms to the set of selected atoms (requires {0})"),
319         "set pickingStyle DRAG/EXTENDEDSELECT"));
320     newAction(ACTION_selectToggle, "_selectToggle", GT.o(GT.$(
321         "toggle selection (requires {0})"),
322         "set pickingStyle DRAG/EXTENDEDSELECT/RASMOL"));
323     newAction(
324         ACTION_selectToggleExtended,
325         "_selectToggleOr",
326         GT.o(GT
327             .$(
328                 "if all are selected, unselect all, otherwise add this group of atoms to the set of selected atoms (requires {0})"),
329                 "set pickingStyle DRAG"));
330     newAction(ACTION_setMeasure, "_setMeasure", GT
331         .$("pick an atom to initiate or conclude a measurement"));
332     newAction(ACTION_slab, "_slab", GT.o(GT.$(
333         "adjust slab (front plane; requires {0})"), "SLAB ON"));
334     newAction(ACTION_slabAndDepth, "_slabAndDepth", GT.o(GT.$(
335         "move slab/depth window (both planes; requires {0})"), "SLAB ON"));
336     newAction(ACTION_slideZoom, "_slideZoom", GT
337         .$("zoom (along right edge of window)"));
338     newAction(
339         ACTION_spinDrawObjectCCW,
340         "_spinDrawObjectCCW",
341         GT.o(GT
342             .$(
343                 "click on two points to spin around axis counterclockwise (requires {0})"),
344                 "set picking SPIN"));
345     newAction(ACTION_spinDrawObjectCW, "_spinDrawObjectCW", GT.o(GT.$(
346         "click on two points to spin around axis clockwise (requires {0})"),
347         "set picking SPIN"));
348     newAction(ACTION_stopMotion, "_stopMotion", GT.o(GT.$(
349         "stop motion (requires {0})"), "set waitForMoveTo FALSE"));
350     newAction(
351         ACTION_swipe,
352         "_swipe",
353         GT
354             .$("spin model (swipe and release button and stop motion simultaneously)"));
355     newAction(ACTION_translate, "_translate", GT.$("translate"));
356     newAction(ACTION_wheelZoom, "_wheelZoom", GT.$("zoom"));
357   }
358 
getActionName(int i)359   public static String getActionName(int i) {
360     return (i < actionNames.length ? actionNames[i] : null);
361   }
362 
getActionFromName(String name)363   public static int getActionFromName(String name) {
364     for (int i = 0; i < actionNames.length; i++)
365       if (actionNames[i].equalsIgnoreCase(name))
366         return i;
367     return -1;
368   }
369 
getBindingInfo(String qualifiers)370   public String getBindingInfo(String qualifiers) {
371     return b.getBindingInfo(actionInfo, actionNames, qualifiers);
372   }
373 
setBinding(Binding newBinding)374   protected void setBinding(Binding newBinding) {
375     // overridden in ActionManagerMT
376     b = newBinding;
377   }
378 
bnd(int mouseAction, int... jmolActions)379   boolean bnd(int mouseAction, int... jmolActions) {
380     for (int i = jmolActions.length; --i >= 0;)
381       if (b.isBound(mouseAction, jmolActions[i]))
382         return true;
383     return false;
384   }
385 
isDrawOrLabelAction(int a)386   private boolean isDrawOrLabelAction(int a) {
387     return (drawMode && bnd(a, ACTION_dragDrawObject, ACTION_dragDrawPoint)
388         || labelMode && bnd(a, ACTION_dragLabel));
389   }
390 
391   /**
392    * picking modes set picking....
393    */
394 
395   private int apm = PICKING_IDENTIFY;
396   private int bondPickingMode;
397 
getBondPickingMode()398   public int getBondPickingMode() {
399     return bondPickingMode;
400   }
401 
402   public final static int PICKING_MK_RESET = -1;
403   public final static int PICKING_OFF = 0;
404   public final static int PICKING_IDENTIFY = 1;
405   public final static int PICKING_LABEL = 2;
406   public final static int PICKING_CENTER = 3;
407   public final static int PICKING_DRAW = 4;
408   public final static int PICKING_SPIN = 5;
409   public final static int PICKING_SYMMETRY = 6;
410   public final static int PICKING_DELETE_ATOM = 7;
411   public final static int PICKING_DELETE_BOND = 8;
412   public final static int PICKING_SELECT_ATOM = 9;
413   public final static int PICKING_SELECT_GROUP = 10;
414   public final static int PICKING_SELECT_CHAIN = 11;
415   public final static int PICKING_SELECT_MOLECULE = 12;
416   public final static int PICKING_SELECT_POLYMER = 13;
417   public final static int PICKING_SELECT_STRUCTURE = 14;
418   public final static int PICKING_SELECT_SITE = 15;
419   public final static int PICKING_SELECT_MODEL = 16;
420   public final static int PICKING_SELECT_ELEMENT = 17;
421   public final static int PICKING_MEASURE = 18;
422   public final static int PICKING_MEASURE_DISTANCE = 19;
423   public final static int PICKING_MEASURE_ANGLE = 20;
424   public final static int PICKING_MEASURE_TORSION = 21;
425   public final static int PICKING_MEASURE_SEQUENCE = 22;
426   public final static int PICKING_NAVIGATE = 23;
427   public final static int PICKING_CONNECT = 24;
428   public final static int PICKING_STRUTS = 25;
429   public final static int PICKING_DRAG_SELECTED = 26;
430   public final static int PICKING_DRAG_MOLECULE = 27;
431   public final static int PICKING_DRAG_ATOM = 28;
432   public final static int PICKING_DRAG_MINIMIZE = 29;
433   public final static int PICKING_DRAG_MINIMIZE_MOLECULE = 30; // for docking
434   public final static int PICKING_INVERT_STEREO = 31;
435   public final static int PICKING_ASSIGN_ATOM = 32;
436   public final static int PICKING_ASSIGN_BOND = 33;
437   public final static int PICKING_ROTATE_BOND = 34;
438   public final static int PICKING_IDENTIFY_BOND = 35;
439   public final static int PICKING_DRAG_LIGAND = 36;
440   public final static int PICKING_DRAG_MODEL = 37;
441 
442   /**
443    * picking styles
444    */
445   public final static int PICKINGSTYLE_SELECT_JMOL = 0;
446   public final static int PICKINGSTYLE_SELECT_CHIME = 0;
447   public final static int PICKINGSTYLE_SELECT_RASMOL = 1;
448   public final static int PICKINGSTYLE_SELECT_PFAAT = 2;
449   public final static int PICKINGSTYLE_SELECT_DRAG = 3;
450   public final static int PICKINGSTYLE_MEASURE_ON = 4;
451   public final static int PICKINGSTYLE_MEASURE_OFF = 5;
452 
453   private final static String[] pickingModeNames;
454   static {
455     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 dragselected dragmolecule dragatom dragminimize dragminimizemolecule invertstereo assignatom assignbond rotatebond identifybond dragligand dragmodel".split(" ");
456   }
457 
getPickingModeName(int pickingMode)458   public final static String getPickingModeName(int pickingMode) {
459     return (pickingMode < 0 || pickingMode >= pickingModeNames.length ? "off"
460         : pickingModeNames[pickingMode]);
461   }
462 
getPickingMode(String str)463   public final static int getPickingMode(String str) {
464     for (int i = pickingModeNames.length; --i >= 0;)
465       if (str.equalsIgnoreCase(pickingModeNames[i]))
466         return i;
467     return -1;
468   }
469 
470   private final static String[] pickingStyleNames;
471 
472   static {
473     pickingStyleNames = "toggle selectOrToggle extendedSelect drag measure measureoff".split(" ");
474   }
475 
getPickingStyleName(int pickingStyle)476   public final static String getPickingStyleName(int pickingStyle) {
477     return (pickingStyle < 0 || pickingStyle >= pickingStyleNames.length ? "toggle"
478         : pickingStyleNames[pickingStyle]);
479   }
480 
getPickingStyleIndex(String str)481   public final static int getPickingStyleIndex(String str) {
482     for (int i = pickingStyleNames.length; --i >= 0;)
483       if (str.equalsIgnoreCase(pickingStyleNames[i]))
484         return i;
485     return -1;
486   }
487 
getAtomPickingMode()488   public int getAtomPickingMode() {
489     return apm;
490   }
491 
setPickingMode(int pickingMode)492   public void setPickingMode(int pickingMode) {
493     boolean isNew = false;
494     switch (pickingMode) {
495     case PICKING_MK_RESET:
496       // from  set modelkit OFF only
497       isNew = true;
498       bondPickingMode = PICKING_IDENTIFY_BOND;
499       pickingMode = PICKING_IDENTIFY;
500       vwr.setStringProperty("pickingStyle", "toggle");
501       vwr.setBooleanProperty("bondPicking", false);
502       break;
503     case PICKING_IDENTIFY_BOND:
504     case PICKING_ROTATE_BOND:
505     case PICKING_ASSIGN_BOND:
506     case PICKING_DELETE_BOND:
507       vwr.setBooleanProperty("bondPicking", true);
508       bondPickingMode = pickingMode;
509 //      return;
510 //    case PICKING_DELETE_BOND:
511 //      bondPickingMode = pickingMode;
512 //      if (vwr.getBondsPickable())
513 //        return;
514       resetMeasurement();
515       return;
516 //      isNew = true;
517 //      break;
518     // if we have bondPicking mode, then we don't set atomPickingMode to this
519     }
520     isNew |= (apm != pickingMode);
521     apm = pickingMode;
522     if (isNew)
523       resetMeasurement();
524   }
525 
526   private int pickingStyle;
527   private int pickingStyleSelect = PICKINGSTYLE_SELECT_JMOL;
528   private int pickingStyleMeasure = PICKINGSTYLE_MEASURE_OFF;
529   private int rootPickingStyle = PICKINGSTYLE_SELECT_JMOL;
530 
531 
getPickingState()532   public String getPickingState() {
533     // the pickingMode is not reported in the state. But when we do an UNDO,
534     // we want to restore this.
535     String script = ";set modelkitMode " + vwr.getBoolean(T.modelkitmode)
536         + ";set picking " + getPickingModeName(apm);
537     if (apm == PICKING_ASSIGN_ATOM)
538       script += "_" + vwr.getModelkitProperty("atomType");
539     script += ";";
540     if (bondPickingMode != PICKING_OFF)
541       script += "set picking " + getPickingModeName(bondPickingMode);
542     if (bondPickingMode == PICKING_ASSIGN_BOND)
543       script += "_" + vwr.getModelkitProperty("bondType");
544     script += ";";
545     return script;
546   }
547 
getPickingStyle()548   int getPickingStyle() {
549     return pickingStyle;
550   }
551 
setPickingStyle(int pickingStyle)552   void setPickingStyle(int pickingStyle) {
553     this.pickingStyle = pickingStyle;
554     if (pickingStyle >= PICKINGSTYLE_MEASURE_ON) {
555       pickingStyleMeasure = pickingStyle;
556       resetMeasurement();
557     } else {
558       if (pickingStyle < PICKINGSTYLE_SELECT_DRAG)
559         rootPickingStyle = pickingStyle;
560       pickingStyleSelect = pickingStyle;
561     }
562     rubberbandSelectionMode = false;
563     switch (pickingStyleSelect) {
564     case PICKINGSTYLE_SELECT_PFAAT:
565       if (!b.name.equals("extendedSelect"))
566         setBinding(pfaatBinding == null ? pfaatBinding = Binding
567             .newBinding(vwr, "Pfaat") : pfaatBinding);
568       break;
569     case PICKINGSTYLE_SELECT_DRAG:
570       if (!b.name.equals("drag"))
571         setBinding(dragBinding == null ? dragBinding = Binding
572             .newBinding(vwr, "Drag") : dragBinding);
573       rubberbandSelectionMode = true;
574       break;
575     case PICKINGSTYLE_SELECT_RASMOL:
576       if (!b.name.equals("selectOrToggle"))
577         setBinding(rasmolBinding == null ? rasmolBinding = Binding
578             .newBinding(vwr, "Rasmol") : rasmolBinding);
579       break;
580     default:
581       if (b != jmolBinding)
582         setBinding(jmolBinding);
583     }
584     if (!b.name.equals("drag"))
585       predragBinding = b;
586   }
587 
588 
589 
590   private final static long MAX_DOUBLE_CLICK_MILLIS = 700;
591   protected final static long MININUM_GESTURE_DELAY_MILLISECONDS = 10;
592   private final static int SLIDE_ZOOM_X_PERCENT = 98;
593   public final static float DEFAULT_MOUSE_DRAG_FACTOR = 1f;
594   public final static float DEFAULT_MOUSE_WHEEL_FACTOR = 1.15f;
595   public final static float DEFAULT_GESTURE_SWIPE_FACTOR = 1f;
596 
597 
598   public final static int XY_RANGE = 10; // BH 2019.04.21 was 0
599 
600   private float gestureSwipeFactor = DEFAULT_GESTURE_SWIPE_FACTOR;
601   protected float mouseDragFactor = DEFAULT_MOUSE_DRAG_FACTOR;
602   protected float mouseWheelFactor = DEFAULT_MOUSE_WHEEL_FACTOR;
603 
setGestureSwipeFactor(float factor)604   void setGestureSwipeFactor(float factor) {
605     gestureSwipeFactor = factor;
606   }
607 
setMouseDragFactor(float factor)608   void setMouseDragFactor(float factor) {
609     mouseDragFactor = factor;
610   }
611 
setMouseWheelFactor(float factor)612   void setMouseWheelFactor(float factor) {
613     mouseWheelFactor = factor;
614   }
615 
616   protected final MouseState current = new MouseState("current");
617   protected final MouseState moved = new MouseState("moved");
618   private final MouseState clicked = new MouseState("clicked");
619   private final MouseState pressed = new MouseState("pressed");
620   private final MouseState dragged = new MouseState("dragged");
621 
isDraggedIsShiftDown()622   boolean isDraggedIsShiftDown() {
623     return (dragged.modifiers & Binding.SHIFT) != 0;
624   }
setCurrent(long time, int x, int y, int mods)625   protected void setCurrent(long time, int x, int y, int mods) {
626     vwr.hoverOff();
627     current.set(time, x, y, mods);
628   }
629 
getCurrentX()630   int getCurrentX() {
631     return current.x;
632   }
633 
getCurrentY()634   int getCurrentY() {
635     return current.y;
636   }
637 
638   protected int pressedCount;
639   protected int clickedCount;
640 
641   private boolean drawMode;
642   private boolean labelMode;
643   private boolean dragSelectedMode;
644   private boolean measuresEnabled = true;
645   private boolean haveSelection;
646 
setMouseMode()647   public void setMouseMode() {
648     drawMode = labelMode = false;
649     dragSelectedMode = vwr.getDragSelected();
650     measuresEnabled = !dragSelectedMode;
651     if (!dragSelectedMode)
652       switch (apm) {
653       default:
654         return;
655       case PICKING_ASSIGN_ATOM:
656         measuresEnabled = !vwr.getModelkit(false).isPickAtomAssignCharge();
657         return;
658       case PICKING_DRAW:
659         drawMode = true;
660         // drawMode and dragSelectedMode are incompatible
661         measuresEnabled = false;
662         break;
663       //other cases here?
664       case PICKING_LABEL:
665         labelMode = true;
666         measuresEnabled = false;
667         break;
668       case PICKING_SELECT_ATOM:
669         measuresEnabled = false;
670         break;
671       case PICKING_MEASURE_DISTANCE:
672       case PICKING_MEASURE_SEQUENCE:
673       case PICKING_MEASURE_ANGLE:
674       case PICKING_MEASURE_TORSION:
675         measuresEnabled = false;
676         return;
677         //break;
678       }
679     exitMeasurementMode(null);
680   }
681 
clearMouseInfo()682   protected void clearMouseInfo() {
683     // when a second touch is made, this clears all record of first touch
684     pressedCount = clickedCount = 0;
685     dragGesture.setAction(0, 0);
686     exitMeasurementMode(null);
687   }
688 
689   private boolean hoverActive = false;
690 
691   private MeasurementPending mp;
692 
693   private int dragAtomIndex = -1;
694 
695   /**
696    * set to true in checkPressedAction if the screen coordinates of the press
697    * are relatively close to the coordinates of the hover that highlighted the bond,
698    * indicating that we can alias a left-click to a shift-left-click
699    *
700    */
701   private boolean mkBondPressed;
702 
setDragAtomIndex(int iatom)703   public void setDragAtomIndex(int iatom) {
704     // from label
705     dragAtomIndex = iatom;
706     setAtomsPicked(BSUtil.newAndSetBit(iatom), "Label picked for atomIndex = " + iatom);
707   }
708 
709 
710   private boolean rubberbandSelectionMode = false;
711   private final Rectangle rectRubber = new Rectangle();
712 
713   private boolean isAltKeyReleased = true;
714   private boolean keyProcessing;
715 
716   protected boolean isMultiTouchClient;
717   protected boolean isMultiTouchServer;
718 
isMTClient()719   public boolean isMTClient() {
720     return isMultiTouchClient;
721   }
722 
isMTServer()723   public boolean isMTServer() {
724     return isMultiTouchServer;
725   }
726 
dispose()727   public void dispose() {
728     clear();
729   }
730 
clear()731   public void clear() {
732     startHoverWatcher(false);
733     if (predragBinding != null)
734       b = predragBinding;
735     vwr.setPickingMode(null, PICKING_IDENTIFY);
736     vwr.setPickingStyle(null, rootPickingStyle);
737     isAltKeyReleased = true;
738   }
739 
startHoverWatcher(boolean isStart)740   synchronized public void startHoverWatcher(boolean isStart) {
741     if (vwr.isPreviewOnly)
742       return;
743     try {
744       if (isStart) {
745         if (hoverWatcherThread != null)
746           return;
747         current.time = -1;
748         hoverWatcherThread = new HoverWatcherThread(this, current, moved,
749             vwr);
750       } else {
751         if (hoverWatcherThread == null)
752           return;
753         current.time = -1;
754         hoverWatcherThread.interrupt();
755         hoverWatcherThread = null;
756       }
757     } catch (Exception e) {
758       // is possible -- seen once hoverWatcherThread.start() had null pointer.
759     }
760   }
761 
762   /**
763    * only NONE (-1) is implemented; it just stops the hoverWatcher thread so
764    * that the vwr references are all removed
765    *
766    * @param modeMouse
767    */
setModeMouse(int modeMouse)768   public void setModeMouse(int modeMouse) {
769     if (modeMouse == JC.MOUSE_NONE) {
770       startHoverWatcher(false);
771     }
772   }
773 
774   /**
775    * called by MouseManager.keyPressed
776    *
777    * @param key
778    * @param modifiers
779    * @return true if handled
780    */
781   @Override
keyPressed(int key, int modifiers)782   public boolean keyPressed(int key, int modifiers) {
783     if (keyProcessing)
784       return false;
785     keyProcessing = true;
786     switch (key) {
787     case Event.VK_ALT:
788       if (dragSelectedMode && isAltKeyReleased)
789         vwr.moveSelected(Integer.MIN_VALUE, 0, Integer.MIN_VALUE,
790             Integer.MIN_VALUE, Integer.MIN_VALUE, null, false, false, modifiers);
791       isAltKeyReleased = false;
792       moved.modifiers |= Binding.ALT;
793       break;
794     case Event.VK_SHIFT:
795       dragged.modifiers |= Binding.SHIFT;
796       moved.modifiers |= Binding.SHIFT;
797       break;
798     case Event.VK_CONTROL:
799       moved.modifiers |= Binding.CTRL;
800       break;
801     case Event.VK_ESCAPE:
802       vwr.hoverOff();
803       exitMeasurementMode("escape");
804       break;
805     default:
806       vwr.hoverOff();
807       break;
808     }
809     int action = Binding.LEFT | Binding.SINGLE | Binding.DRAG | moved.modifiers;
810     if (!labelMode && !b.isUserAction(action)) {
811       checkMotionRotateZoom(action, current.x, 0, 0, false);
812     }
813     if (vwr.getBoolean(T.navigationmode)) {
814       // if (vwr.getBooleanProperty("showKeyStrokes", false))
815       // vwr.evalStringQuiet("!set echo bottom left;echo "
816       // + (i == 0 ? "" : i + " " + m));
817       switch (key) {
818       case Event.VK_UP:
819       case Event.VK_DOWN:
820       case Event.VK_LEFT:
821       case Event.VK_RIGHT:
822       case Event.VK_SPACE:
823       case Event.VK_PERIOD:
824         vwr.navigate(key, modifiers);
825         break;
826       }
827     }
828     keyProcessing = false;
829     return true;
830   }
831 
832   @Override
keyTyped(int keyChar, int modifiers)833   public boolean keyTyped(int keyChar, int modifiers) {
834     return false;
835   }
836 
837   @Override
keyReleased(int key)838   public void keyReleased(int key) {
839     switch (key) {
840     case Event.VK_ALT:
841       moved.modifiers &= ~Binding.ALT;
842       if (dragSelectedMode)
843         vwr.moveSelected(Integer.MAX_VALUE, 0, Integer.MIN_VALUE,
844             Integer.MIN_VALUE, Integer.MIN_VALUE, null, false, false, moved.modifiers);
845       isAltKeyReleased = true;
846       break;
847     case Event.VK_SHIFT:
848       moved.modifiers &= ~Binding.SHIFT;
849       break;
850     case Event.VK_CONTROL:
851       moved.modifiers &= ~Binding.CTRL;
852     }
853     if (moved.modifiers == 0)
854       vwr.setCursor(GenericPlatform.CURSOR_DEFAULT);
855     if (!vwr.getBoolean(T.navigationmode))
856       return;
857     //if (vwr.getBooleanProperty("showKeyStrokes", false))
858     //vwr.evalStringQuiet("!set echo bottom left;echo;");
859     switch (key) {
860     case Event.VK_UP:
861     case Event.VK_DOWN:
862     case Event.VK_LEFT:
863     case Event.VK_RIGHT:
864       vwr.navigate(0, 0);
865       break;
866     }
867   }
868 
869   @Override
mouseEnterExit(long time, int x, int y, boolean isExit)870   public void mouseEnterExit(long time, int x, int y, boolean isExit) {
871     if (vwr.tm.stereoDoubleDTI)
872       x = x << 1;
873     setCurrent(time, x, y, 0);
874     if (isExit)
875       exitMeasurementMode("mouseExit"); //otherwise pending measurement can be left over.
876   }
877 
878   private int pressAction;
879   private int dragAction;
880   private int clickAction;
881 
setMouseActions(int count, int buttonMods, boolean isRelease)882   private void setMouseActions(int count, int buttonMods, boolean isRelease) {
883     pressAction = Binding.getMouseAction(count, buttonMods,
884         isRelease ? Event.RELEASED : Event.PRESSED);
885     dragAction = Binding.getMouseAction(count, buttonMods, Event.DRAGGED);
886     clickAction = Binding.getMouseAction(count, buttonMods, Event.CLICKED);
887   }
888 
889   /**
890    *
891    * @param mode
892    *        MOVED PRESSED DRAGGED RELEASED CLICKED WHEELED
893    * @param time
894    * @param x
895    * @param y
896    * @param count
897    * @param buttonMods
898    *        LEFT RIGHT MIDDLE WHEEL SHIFT ALT CTRL
899    */
900   @Override
mouseAction(int mode, long time, int x, int y, int count, int buttonMods)901   public void mouseAction(int mode, long time, int x, int y, int count,
902                           int buttonMods) {
903     if (!vwr.getMouseEnabled())
904       return;
905     if (Logger.debuggingHigh && mode != Event.MOVED && vwr.getBoolean(T.testflag1))
906       vwr.showString("mouse action: " + mode + " " + buttonMods + " " + Binding.getMouseActionName(Binding.getMouseAction(count, buttonMods, mode), false), false);
907     if (vwr.tm.stereoDoubleDTI)
908       x = x << 1;
909     switch (mode) {
910     case Event.MOVED:
911       setCurrent(time, x, y, buttonMods);
912       moved.setCurrent(current, 0);
913       if (mp != null || hoverActive) {
914         clickAction = Binding.getMouseAction(clickedCount, buttonMods,
915             Event.MOVED);
916         checkClickAction(x, y, time, 0);
917         return;
918       }
919       if (isZoomArea(x)) {
920         checkMotionRotateZoom(LEFT_DRAGGED, 0, 0, 0, false);
921         return;
922       }
923       if (vwr.currentCursor == GenericPlatform.CURSOR_ZOOM)
924         vwr.setCursor(GenericPlatform.CURSOR_DEFAULT);
925       return;
926     case Event.PRESSED:
927       setMouseMode();
928       pressedCount = (pressed.check(20, x, y, buttonMods, time,
929           MAX_DOUBLE_CLICK_MILLIS) ? pressedCount + 1 : 1);
930       if (pressedCount == 1) {
931         vwr.checkInMotion(1);
932         setCurrent(time, x, y, buttonMods);
933       }
934       pressAction = Binding.getMouseAction(pressedCount, buttonMods,
935           Event.PRESSED);
936       vwr.setCursor(GenericPlatform.CURSOR_HAND);
937       pressed.setCurrent(current, 1);
938       dragged.setCurrent(current, 1);
939       vwr.setFocus();
940       dragGesture.setAction(dragAction, time);
941       checkPressedAction(x, y, time);
942       return;
943     case Event.DRAGGED:
944       setMouseMode();
945       setMouseActions(pressedCount, buttonMods, false);
946       int deltaX = x - dragged.x;
947       int deltaY = y - dragged.y;
948       setCurrent(time, x, y, buttonMods);
949       dragged.setCurrent(current, -1);
950       //if (false && apm != PICKING_ASSIGN_ATOM
951       //    && apm != ACTION_pickMeasure
952       //    && apm != PICKING_MEASURE_DISTANCE)
953       dragGesture.add(dragAction, x, y, time);
954       checkDragWheelAction(dragAction, x, y, deltaX, deltaY, time,
955           Event.DRAGGED);
956       return;
957     case Event.RELEASED:
958       setMouseActions(pressedCount, buttonMods, true);
959       setCurrent(time, x, y, buttonMods);
960       vwr.spinXYBy(0, 0, 0);
961       boolean dragRelease = !pressed.check(XY_RANGE, x, y, buttonMods, time,
962           Long.MAX_VALUE);
963       checkReleaseAction(x, y, time, dragRelease);
964       return;
965     case Event.WHEELED:
966       if (vwr.isApplet && !vwr.hasFocus())
967         return;
968       setCurrent(time, current.x, current.y, buttonMods);
969       checkDragWheelAction(Binding.getMouseAction(0, buttonMods,
970           Event.WHEELED), current.x, current.y, 0, y, time, Event.WHEELED);
971       return;
972     case Event.CLICKED:
973       setMouseMode();
974       // xyRange was 0 BH 2019.04.21
975       clickedCount = (count > 1 ? count : clicked.check(XY_RANGE, 0, 0, buttonMods,
976           time, MAX_DOUBLE_CLICK_MILLIS) ? clickedCount + 1 : 1);
977       if (clickedCount == 1) {
978         setCurrent(time, x, y, buttonMods);
979       }
980       setMouseActions(clickedCount, buttonMods, false);
981       clicked.setCurrent(current, clickedCount);
982       vwr.setFocus();
983       if (apm != PICKING_SELECT_ATOM
984           && bnd(Binding.getMouseAction(1, buttonMods, Event.PRESSED),
985               ACTION_selectAndDrag))
986         return;
987       clickAction = Binding.getMouseAction(clickedCount, buttonMods,
988           Event.CLICKED);
989       checkClickAction(x, y, time, clickedCount);
990       return;
991     }
992   }
993 
checkPressedAction(int x, int y, long time)994   private void checkPressedAction(int x, int y, long time) {
995     int buttonMods = Binding.getButtonMods(pressAction);
996     boolean isDragSelectedAction = bnd(
997         Binding.getMouseAction(1, buttonMods, Event.PRESSED),
998         ACTION_selectAndDrag);
999     if (buttonMods != 0) {
1000       pressAction = vwr.notifyMouseClicked(x, y, pressAction, Event.PRESSED);
1001       if (pressAction == 0)
1002         return;
1003       buttonMods = Binding.getButtonMods(pressAction);
1004     }
1005     setMouseActions(pressedCount, buttonMods, false);
1006     if (Logger.debuggingHigh && vwr.getBoolean(T.testflag1))
1007       Logger.debug(Binding.getMouseActionName(pressAction, false));
1008 
1009     if (isDrawOrLabelAction(dragAction) && vwr.checkObjectDragged(Integer.MIN_VALUE, 0, x, y, dragAction))
1010       return;
1011     checkUserAction(pressAction, x, y, 0, 0, time, Event.PRESSED);
1012     boolean isBound = false;
1013 
1014     switch (apm) {
1015     case PICKING_ASSIGN_ATOM:
1016       isBound = bnd(clickAction, ACTION_assignNew);
1017       break;
1018     case PICKING_DRAG_ATOM:
1019       isBound = bnd(dragAction, ACTION_dragAtom, ACTION_dragZ);
1020       break;
1021     case PICKING_DRAG_SELECTED:
1022     case PICKING_DRAG_LIGAND:
1023     case PICKING_DRAG_MODEL:
1024     case PICKING_DRAG_MOLECULE:
1025       isBound = bnd(dragAction, ACTION_dragAtom, ACTION_dragZ, ACTION_rotateSelected);
1026       break;
1027     case PICKING_DRAG_MINIMIZE:
1028       isBound = bnd(dragAction, ACTION_dragMinimize, ACTION_dragZ);
1029       break;
1030     case PICKING_DRAG_MINIMIZE_MOLECULE:
1031       isBound = bnd(dragAction, ACTION_dragMinimize, ACTION_dragZ, ACTION_rotateSelected);
1032       break;
1033     }
1034     if (isBound) {
1035       dragAtomIndex = vwr.findNearestAtomIndexMovable(x, y, true);
1036       if (dragAtomIndex >= 0
1037           && (apm == PICKING_ASSIGN_ATOM || apm == PICKING_INVERT_STEREO)
1038 //          && vwr.ms.isAtomInLastModel(dragAtomIndex)
1039           ) {
1040         if (bondPickingMode == PICKING_ROTATE_BOND) {
1041           vwr.setModelkitProperty("bondAtomIndex", Integer.valueOf(dragAtomIndex));
1042         }
1043         enterMeasurementMode(dragAtomIndex);
1044         mp.addPoint(dragAtomIndex, null, false);
1045       }
1046       int[] xy = (int[]) vwr.getModelkitProperty("screenXY");
1047         mkBondPressed = (xy != null && pressed.inRange(10, xy[0], xy[1]));
1048 
1049       return;
1050     }
1051     if (bnd(pressAction, ACTION_popupMenu)) {
1052       char type = 'j';
1053       if (vwr.getBoolean(T.modelkitmode)) {
1054         Map<String, Object> t = vwr.checkObjectClicked(x, y, LEFT_CLICKED);
1055         type = (
1056 //            t != null && "bond".equals(t.get("type")) ? 'b' : vwr
1057 //            .findNearestAtomIndex(x, y) >= 0 ? 'a' :
1058               'm');
1059       }
1060       vwr.popupMenu(x, y, type);
1061       return;
1062     }
1063     if (dragSelectedMode) {
1064       haveSelection = (!isDragSelectedAction || vwr
1065           .findNearestAtomIndexMovable(x, y, true) >= 0);
1066       if (haveSelection && bnd(dragAction, ACTION_dragSelected, ACTION_dragZ))
1067         vwr.moveSelected(Integer.MIN_VALUE, 0, Integer.MIN_VALUE,
1068             Integer.MIN_VALUE, Integer.MIN_VALUE, null, false, false, buttonMods);
1069       return;
1070     }
1071     //   if (vwr.g.useArcBall)
1072     //      vwr.rotateArcBall(x, y, 0);
1073     checkMotionRotateZoom(dragAction, x, 0, 0, true);
1074   }
1075 
checkDragWheelAction(int dragWheelAction, int x, int y, int deltaX, int deltaY, long time, int mode)1076   private void checkDragWheelAction(int dragWheelAction, int x, int y,
1077                                     int deltaX, int deltaY, long time, int mode) {
1078     int buttonmods = Binding.getButtonMods(dragWheelAction);
1079     if (buttonmods != 0) {
1080       int newAction = vwr.notifyMouseClicked(x, y,
1081           Binding.getMouseAction(pressedCount, buttonmods, mode), mode); // why was this "-pressedCount"? passing to user?
1082       if (newAction == 0)
1083         return;
1084       if (newAction > 0)
1085         dragWheelAction = newAction;
1086     }
1087 
1088     if (isRubberBandSelect(dragWheelAction)) {
1089       calcRectRubberBand();
1090       vwr.refresh(Viewer.REFRESH_SYNC_MASK, "rubberBand selection");
1091       return;
1092     }
1093 
1094     if (checkUserAction(dragWheelAction, x, y, deltaX, deltaY, time, mode))
1095       return;
1096 
1097     if (vwr.g.modelKitMode && vwr.getModelkit(false).getRotateBondIndex() >= 0) {
1098       if (dragAtomIndex >= 0 || mkBondPressed
1099           || bnd(dragWheelAction, ACTION_rotateBranch)) {
1100         vwr.moveSelected(deltaX, deltaY, Integer.MIN_VALUE, x, y, null, false,
1101             false, dragAtomIndex >= 0 ? 0 : Event.VK_SHIFT);
1102         return;
1103       }
1104     }
1105 
1106     BS bs = null;
1107     if (dragAtomIndex >= 0 && apm != PICKING_LABEL) {
1108 
1109       switch (apm) {
1110       case PICKING_DRAG_SELECTED:
1111         dragSelected(dragWheelAction, deltaX, deltaY, true);
1112         return;
1113       case PICKING_DRAG_LIGAND:
1114       case PICKING_DRAG_MODEL:
1115       case PICKING_DRAG_MOLECULE:
1116       case PICKING_DRAG_MINIMIZE_MOLECULE:
1117         bs = vwr.ms.getAtoms((apm == PICKING_DRAG_MODEL ? T.model : T.molecule), BSUtil.newAndSetBit(dragAtomIndex));
1118         if (apm == PICKING_DRAG_LIGAND)
1119           bs.and(vwr.getAtomBitSet("ligand"));
1120         //$FALL-THROUGH$
1121       case PICKING_DRAG_ATOM:
1122       case PICKING_DRAG_MINIMIZE:
1123         if (dragGesture.getPointCount() == 1)
1124           vwr.undoMoveActionClear(dragAtomIndex, AtomCollection.TAINT_COORD,
1125               true);
1126         setMotion(GenericPlatform.CURSOR_MOVE, true);
1127         if (bnd(dragWheelAction, ACTION_rotateSelected)) {
1128           vwr.rotateSelected(getDegrees(deltaX, true),
1129               getDegrees(deltaY, false), bs);
1130         } else {
1131           switch (apm) {
1132           case PICKING_DRAG_LIGAND:
1133           case PICKING_DRAG_MODEL:
1134           case PICKING_DRAG_MOLECULE:
1135           case PICKING_DRAG_MINIMIZE_MOLECULE:
1136             vwr.select(bs, false, 0, true);
1137             break;
1138           }
1139           vwr.moveAtomWithHydrogens(
1140               dragAtomIndex,
1141               deltaX,
1142               deltaY,
1143               (bnd(dragWheelAction, ACTION_dragZ) ? -deltaY : Integer.MIN_VALUE),
1144               bs);
1145         }
1146         // NAH! if (atomPickingMode == PICKING_DRAG_MINIMIZE_MOLECULE && (dragGesture.getPointCount() % 5 == 0))
1147         //  minimize(false);
1148         return;
1149       }
1150     }
1151 
1152     if (dragAtomIndex >= 0 && mode == Event.DRAGGED
1153         && bnd(clickAction, ACTION_assignNew) && apm == PICKING_ASSIGN_ATOM) {
1154       int nearestAtomIndex = vwr.findNearestAtomIndexMovable(x, y, false);
1155       if (nearestAtomIndex >= 0) {
1156         if (mp != null) {
1157           mp.setCount(1);
1158         } else if (measuresEnabled) {
1159           enterMeasurementMode(nearestAtomIndex);
1160         }
1161         addToMeasurement(nearestAtomIndex, null, true);
1162         mp.colix = C.MAGENTA;
1163       } else if (mp != null) {
1164         mp.setCount(1);
1165         mp.colix = C.GOLD;
1166       }
1167       if (mp == null)
1168         return;
1169       if (vwr.antialiased) {
1170         x <<= 1;
1171         y <<= 1;
1172       }
1173 
1174       mp.traceX = x;
1175       mp.traceY = y;
1176       vwr.refresh(Viewer.REFRESH_SYNC_MASK, "assignNew");
1177       return;
1178     }
1179 
1180     if (!drawMode && !labelMode && bnd(dragWheelAction, ACTION_translate)) {
1181       vwr.translateXYBy(deltaX, deltaY);
1182       return;
1183     }
1184     if (dragSelectedMode && haveSelection
1185         && bnd(dragWheelAction, ACTION_dragSelected, ACTION_rotateSelected)) {
1186       // we will drag atoms and either rotate or translate them
1187       // possibly just the atoms or possibly their molecule (decided in Viewer)
1188       int iatom = vwr.bsA().nextSetBit(0);
1189       if (iatom < 0)
1190         return;
1191       if (dragGesture.getPointCount() == 1)
1192         vwr.undoMoveActionClear(iatom, AtomCollection.TAINT_COORD, true);
1193       else
1194         vwr.moveSelected(Integer.MAX_VALUE, 0, Integer.MIN_VALUE,
1195             Integer.MIN_VALUE, Integer.MIN_VALUE, null, false, false, buttonmods);
1196       dragSelected(dragWheelAction, deltaX, deltaY, false);
1197       return;
1198     }
1199 
1200     if (isDrawOrLabelAction(dragWheelAction)) {
1201       setMotion(GenericPlatform.CURSOR_MOVE, true);
1202       if (vwr.checkObjectDragged(dragged.x, dragged.y, x, y, dragWheelAction)) {
1203         return;
1204       }
1205     }
1206     if (checkMotionRotateZoom(dragWheelAction, x, deltaX, deltaY, true)) {
1207       if (vwr.tm.slabEnabled && bnd(dragWheelAction,ACTION_slabAndDepth))
1208         vwr.slabDepthByPixels(deltaY);
1209       else
1210         vwr.zoomBy(deltaY);
1211       return;
1212     }
1213     if (bnd(dragWheelAction, ACTION_rotate)) {
1214       //      if (vwr.g.useArcBall)
1215       //        vwr.rotateArcBall(x, y, mouseDragFactor);
1216       //      else
1217       vwr.rotateXYBy(getDegrees(deltaX, true), getDegrees(deltaY, false));
1218       return;
1219     }
1220     if (bnd(dragWheelAction, ACTION_rotateZorZoom)) {
1221       if (deltaX == 0 && Math.abs(deltaY) > 1) {
1222         // if (deltaY < 0 && deltaX > deltaY || deltaY > 0 && deltaX < deltaY)
1223         setMotion(GenericPlatform.CURSOR_ZOOM, true);
1224         vwr.zoomBy(deltaY + (deltaY > 0 ? -1 : 1));
1225       } else if (deltaY == 0 && Math.abs(deltaX) > 1) {
1226         // if (deltaX < 0 && deltaY > deltaX || deltaX > 0 && deltaY < deltaX)
1227         setMotion(GenericPlatform.CURSOR_MOVE, true);
1228         vwr.rotateZBy(-deltaX + (deltaX > 0 ? 1 : -1), Integer.MAX_VALUE,
1229             Integer.MAX_VALUE);
1230       }
1231       return;
1232     }
1233     if (vwr.tm.slabEnabled) {
1234       if (bnd(dragWheelAction, ACTION_depth)) {
1235         vwr.depthByPixels(deltaY);
1236         return;
1237       }
1238       if (bnd(dragWheelAction, ACTION_slab)) {
1239         vwr.slabByPixels(deltaY);
1240         return;
1241       }
1242       if (bnd(dragWheelAction, ACTION_slabAndDepth)) {
1243         vwr.slabDepthByPixels(deltaY);
1244         return;
1245       }
1246     }
1247     if (bnd(dragWheelAction, ACTION_wheelZoom)) {
1248       zoomByFactor(deltaY, Integer.MAX_VALUE, Integer.MAX_VALUE);
1249       return;
1250     }
1251     if (bnd(dragWheelAction, ACTION_rotateZ)) {
1252       setMotion(GenericPlatform.CURSOR_MOVE, true);
1253       vwr.rotateZBy(-deltaX, Integer.MAX_VALUE, Integer.MAX_VALUE);
1254       return;
1255     }
1256   }
1257 
1258   /**
1259    * change actual coordinates of selected atoms from set dragSeleted TRUE or
1260    * set PICKING DRAGSELECTED
1261    *
1262    * Basically, set dragSelected adds new functionality to Jmol with alt-drag
1263    * and alt-shift drag, and set picking dragSelected replaces the standard
1264    * mouse drag with a move action and also adds rotate and z-shift options.
1265    *
1266    * set dragSelected also allows other picking types, such as set picking SELECT,
1267    * which uses double-click to start rotating/moving another molecule.
1268    *
1269    * @param a
1270    * @param deltaX
1271    * @param deltaY
1272    * @param isPickingDrag
1273    */
dragSelected(int a, int deltaX, int deltaY, boolean isPickingDrag)1274   private void dragSelected(int a, int deltaX, int deltaY, boolean isPickingDrag) {
1275 
1276     // see footnotes below for ^, $, #, and *
1277     //
1278     // settings:^    set picking dragSelected             set dragSelected
1279     //
1280     // move:#                 drag                          alt-shift-drag
1281     // rotate:#*          alt-drag                                alt-drag
1282     // z-shift:#        shift-drag                                  (n/a)
1283     //
1284     // double-click:$  (starts measurement)       (sets selected if set picking SELECT)
1285     //
1286     // # all actions involve whole molecules unless   set allowMoveAtoms TRUE
1287     // ^ set picking dragSelected overrules set dragSelected
1288     // * rotate requires   set allowRotateSelected TRUE
1289     // $ set dragSelected allows setting of a new molecule with double-click when    set picking SELECT
1290     // $ set picking dragSelected allows measurements with double-click, as usual
1291 
1292     setMotion(GenericPlatform.CURSOR_MOVE, true);
1293     if (bnd(a, ACTION_rotateSelected) && vwr.getBoolean(T.allowrotateselected))
1294       vwr.rotateSelected(getDegrees(deltaX, true), getDegrees(deltaY, false),
1295           null);
1296     else
1297       vwr.moveSelected(
1298           deltaX,
1299           deltaY,
1300           (isPickingDrag && bnd(a, ACTION_dragZ) ? -deltaY : Integer.MIN_VALUE),
1301           Integer.MIN_VALUE, Integer.MIN_VALUE, null, true, false, dragged.modifiers);
1302   }
1303 
1304 
checkReleaseAction(int x, int y, long time, boolean dragRelease)1305   private void checkReleaseAction(int x, int y, long time, boolean dragRelease) {
1306     if (Logger.debuggingHigh && vwr.getBoolean(T.testflag1))
1307       Logger.debug(Binding.getMouseActionName(pressAction, false));
1308     vwr.checkInMotion(0);
1309     vwr.setInMotion(false);
1310     vwr.setCursor(GenericPlatform.CURSOR_DEFAULT);
1311     dragGesture.add(dragAction, x, y, time);
1312     // necessary for reactivating
1313 //    if (dragRelease)
1314 //      vwr.setModelKitRotateBondIndex(Integer.MIN_VALUE);
1315     if (dragAtomIndex >= 0) {
1316       if (apm == PICKING_DRAG_MINIMIZE
1317           || apm == PICKING_DRAG_MINIMIZE_MOLECULE)
1318         minimize(true);
1319     }
1320     if (apm == PICKING_ASSIGN_ATOM
1321         && bnd(clickAction, ACTION_assignNew)) {
1322       if (mp == null || dragAtomIndex < 0) {
1323         exitMeasurementMode(null);
1324         return;
1325       } else if (bondPickingMode == PICKING_ROTATE_BOND) {
1326         vwr.setModelkitProperty("bondAtomIndex", Integer.valueOf(dragAtomIndex));
1327         exitMeasurementMode(null);
1328         return;
1329       }
1330       assignNew(x, y);
1331       return;
1332     }
1333     dragAtomIndex = -1;
1334     mkBondPressed = false;
1335     boolean isRbAction = isRubberBandSelect(dragAction);
1336     if (isRbAction)
1337       selectRb(clickAction);
1338     rubberbandSelectionMode = (b.name.equals("drag"));
1339     rectRubber.x = Integer.MAX_VALUE;
1340     if (dragRelease) {
1341       vwr.notifyMouseClicked(x, y, Binding.getMouseAction(pressedCount, 0,
1342           Event.RELEASED), Event.RELEASED);
1343     }
1344     if (isDrawOrLabelAction(dragAction)) {
1345       vwr.checkObjectDragged(Integer.MAX_VALUE, 0, x, y, dragAction);
1346       return;
1347     }
1348     if (haveSelection && dragSelectedMode && bnd(dragAction, ACTION_dragSelected))
1349       vwr.moveSelected(Integer.MAX_VALUE, 0, Integer.MIN_VALUE,
1350           Integer.MIN_VALUE, Integer.MIN_VALUE, null, false, false, dragged.modifiers);
1351 
1352     if (dragRelease
1353         && checkUserAction(pressAction, x, y, 0, 0, time, Event.RELEASED))
1354       return;
1355 
1356     if (vwr.getBoolean(T.allowgestures)) {
1357       if (bnd(dragAction, ACTION_swipe)) {
1358         float speed = getExitRate();
1359         if (speed > 0)
1360           vwr.spinXYBy(dragGesture.getDX(4, 2), dragGesture.getDY(4, 2),
1361               speed * 30 * gestureSwipeFactor);
1362         if (vwr.g.logGestures)
1363           vwr.log("$NOW$ swipe " + dragGesture + " " + speed);
1364         return;
1365       }
1366 
1367     }
1368   }
1369 
checkClickAction(int x, int y, long time, int clickedCount)1370   private void checkClickAction(int x, int y, long time, int clickedCount) {
1371     // points are always picked up first, then atoms
1372     // so that atom picking can be superceded by draw picking
1373     // Binding.MOVED is used for some vwr methods.
1374     if (clickedCount > 0) {
1375       if (checkUserAction(clickAction, x, y, 0, 0, time, Binding.CLICK))
1376         return;
1377       clickAction = vwr.notifyMouseClicked(x, y, clickAction, Binding.CLICK);
1378       if (clickAction == 0)
1379         return;
1380     }
1381     if (Logger.debuggingHigh  && vwr.getBoolean(T.testflag1))
1382       Logger.debug(Binding.getMouseActionName(clickAction, false));
1383     if (bnd(clickAction, ACTION_clickFrank)) {
1384       if (vwr.frankClicked(x, y)) {
1385         vwr.popupMenu(-x, y, 'j');
1386         return;
1387       }
1388       if (vwr.frankClickedModelKit(x, y)) {
1389         vwr.popupMenu(10, 0, 'm');
1390         return;
1391       }
1392     }
1393     Point3fi nearestPoint = null;
1394     boolean isBond = false;
1395     boolean isIsosurface = false;
1396     Map<String, Object> map = null;
1397     // t.tok will let us know if this is an atom or a bond that was clicked
1398     if (!drawMode) {
1399       map = vwr.checkObjectClicked(x, y, clickAction);
1400       if (map != null) {
1401         if (labelMode) {
1402           pickLabel(((Integer)map.get("atomIndex")).intValue());
1403           return;
1404         }
1405         isBond = "bond".equals(map.get("type"));
1406         isIsosurface = "isosurface".equals(map.get("type"));
1407         nearestPoint = getPoint(map);
1408       }
1409     }
1410     if (isBond)
1411       clickedCount = 1;
1412 
1413     if (nearestPoint != null && Float.isNaN(nearestPoint.x))
1414       return;
1415     int nearestAtomIndex = findNearestAtom(x, y, nearestPoint, clickedCount > 0);
1416 
1417     if (clickedCount == 0 && apm != PICKING_ASSIGN_ATOM) {
1418       // mouse move
1419       if (mp == null)
1420         return;
1421       if (nearestPoint != null
1422           || mp.getIndexOf(nearestAtomIndex) == 0)
1423         mp.addPoint(nearestAtomIndex, nearestPoint, false);
1424       if (mp.haveModified)
1425         vwr.setPendingMeasurement(mp);
1426       vwr.refresh(Viewer.REFRESH_SYNC_MASK, "measurementPending");
1427       return;
1428     }
1429     setMouseMode();
1430 
1431     if (bnd(clickAction, ACTION_stopMotion)) {
1432       vwr.tm.stopMotion();
1433       // continue checking --- no need to exit here
1434     }
1435 
1436     if (vwr.getBoolean(T.navigationmode)
1437         && apm == PICKING_NAVIGATE
1438         && bnd(clickAction, ACTION_pickNavigate)) {
1439       vwr.navTranslatePercent(x * 100f / vwr.getScreenWidth() - 50f, y
1440           * 100f / vwr.getScreenHeight() - 50f);
1441       return;
1442     }
1443 
1444     // bond change by clicking on a bond
1445     // bond deletion by clicking a bond
1446     if (isBond) {
1447       if (bnd(clickAction, bondPickingMode == PICKING_ROTATE_BOND
1448           || bondPickingMode == PICKING_ASSIGN_BOND ? ACTION_assignNew
1449           : ACTION_deleteBond)) {
1450         bondPicked(((Integer) map.get("index")).intValue());
1451         return;
1452       }
1453     } else if (isIsosurface) {
1454       return;
1455     } else {
1456       if (apm != PICKING_ASSIGN_ATOM && mp != null
1457           && bnd(clickAction, ACTION_pickMeasure)) {
1458         atomOrPointPicked(nearestAtomIndex, nearestPoint);
1459         if (addToMeasurement(nearestAtomIndex, nearestPoint, false) == 4)
1460           toggleMeasurement();
1461         return;
1462       }
1463 
1464       if (bnd(clickAction, ACTION_setMeasure)) {
1465         if (mp != null) {
1466           addToMeasurement(nearestAtomIndex, nearestPoint, true);
1467           toggleMeasurement();
1468         } else if (!drawMode && !labelMode && !dragSelectedMode
1469             && measuresEnabled) {
1470           enterMeasurementMode(nearestAtomIndex);
1471           addToMeasurement(nearestAtomIndex, nearestPoint, true);
1472         }
1473         atomOrPointPicked(nearestAtomIndex, nearestPoint);
1474         return;
1475       }
1476     }
1477     if (isSelectAction(clickAction)) {
1478       // TODO: in drawMode the binding changes
1479       if (!isIsosurface)
1480         atomOrPointPicked(nearestAtomIndex, nearestPoint);
1481       return;
1482     }
1483     if (bnd(clickAction, ACTION_reset)) {
1484       if (nearestAtomIndex < 0)
1485         reset();
1486       return;
1487     }
1488   }
1489 
pickLabel(int iatom)1490   private void pickLabel(int iatom) {
1491     String label = vwr.ms.at[iatom].atomPropertyString(vwr,  T.label);
1492     if (pressedCount == 2) {
1493       label = vwr.apiPlatform.prompt("Set label for atomIndex=" + iatom, label, null, false);
1494       if (label != null) {
1495         vwr.shm.setAtomLabel(label, iatom);
1496         vwr.refresh(Viewer.REFRESH_REPAINT, "label atom");
1497       }
1498     } else {
1499       setAtomsPicked(BSUtil.newAndSetBit(iatom), "Label picked for atomIndex = " + iatom + ": " + label);
1500     }
1501   }
1502 
checkUserAction(int mouseAction, int x, int y, int deltaX, int deltaY, long time, int mode)1503   private boolean checkUserAction(int mouseAction, int x, int y, int deltaX,
1504                                   int deltaY, long time, int mode) {
1505     if (!b.isUserAction(mouseAction))
1506       return false;
1507     boolean passThrough = false;
1508     Object obj;
1509     Map<String, Object> ht = b.getBindings();
1510     String mkey = mouseAction + "\t";
1511     for (String key : ht.keySet()) {
1512       if (key.indexOf(mkey) != 0 || !AU.isAS(obj = ht.get(key)))
1513         continue;
1514       String script = ((String[]) obj)[1];
1515       P3 nearestPoint = null;
1516       if (script.indexOf("_ATOM") >= 0) {
1517         int iatom = findNearestAtom(x, y, null, true);
1518         script = PT.rep(script, "_ATOM", "({"
1519             + (iatom >= 0 ? "" + iatom : "") + "})");
1520         if (iatom >= 0)
1521           script = PT.rep(script, "_POINT", Escape.eP(vwr
1522               .ms.at[iatom]));
1523       }
1524       if (!drawMode
1525           && (script.indexOf("_POINT") >= 0 || script.indexOf("_OBJECT") >= 0 || script
1526               .indexOf("_BOND") >= 0)) {
1527         Map<String, Object> t = vwr.checkObjectClicked(x, y, mouseAction);
1528         if (t != null && (nearestPoint = (P3) t.get("pt")) != null) {
1529           boolean isBond = t.get("type").equals("bond");
1530           if (isBond)
1531             script = PT.rep(script, "_BOND", "[{"
1532                 + t.get("index") + "}]");
1533           script = PT.rep(script, "_POINT", Escape
1534               .eP(nearestPoint));
1535           script = PT.rep(script, "_OBJECT", Escape
1536               .escapeMap(t));
1537         }
1538         script = PT.rep(script, "_BOND", "[{}]");
1539         script = PT.rep(script, "_OBJECT", "{}");
1540       }
1541       script = PT.rep(script, "_POINT", "{}");
1542       script = PT.rep(script, "_ACTION", "" + mouseAction);
1543       script = PT.rep(script, "_X", "" + x);
1544       script = PT.rep(script, "_Y", ""
1545           + (vwr.getScreenHeight() - y));
1546       script = PT.rep(script, "_DELTAX", "" + deltaX);
1547       script = PT.rep(script, "_DELTAY", "" + deltaY);
1548       script = PT.rep(script, "_TIME", "" + time);
1549       script = PT.rep(script, "_MODE", "" + mode);
1550       if (script.startsWith("+:")) {
1551         passThrough = true;
1552         script = script.substring(2);
1553       }
1554       vwr.evalStringQuiet(script);
1555     }
1556     return !passThrough;
1557   }
1558 
1559   /**
1560    *
1561    * @param mouseAction
1562    * @param x
1563    * @param deltaX
1564    * @param deltaY
1565    * @param isDrag
1566    * @return TRUE if motion was a zoom
1567    */
checkMotionRotateZoom(int mouseAction, int x, int deltaX, int deltaY, boolean isDrag)1568   private boolean checkMotionRotateZoom(int mouseAction, int x, int deltaX,
1569                                         int deltaY, boolean isDrag) {
1570     boolean isSlideZoom = bnd(mouseAction, ACTION_slideZoom) && isZoomArea(pressed.x);
1571     boolean isRotateXY = bnd(mouseAction, ACTION_rotate);
1572     boolean isRotateZorZoom = bnd(mouseAction, ACTION_rotateZorZoom);
1573     if (!isSlideZoom && !isRotateXY && !isRotateZorZoom)
1574       return false;
1575     boolean isZoom = (isRotateZorZoom && (deltaX == 0 || Math.abs(deltaY) > 5 * Math
1576         .abs(deltaX)));
1577     int cursor = (isZoom || isZoomArea(moved.x)
1578         || bnd(mouseAction, ACTION_wheelZoom) ? GenericPlatform.CURSOR_ZOOM
1579         : isRotateXY || isRotateZorZoom ? GenericPlatform.CURSOR_MOVE : bnd(
1580             mouseAction, ACTION_center) ? GenericPlatform.CURSOR_HAND
1581             : GenericPlatform.CURSOR_DEFAULT);
1582     setMotion(cursor, isDrag);
1583     return (isZoom || isSlideZoom);
1584   }
1585 
getExitRate()1586   private float getExitRate() {
1587     long dt = dragGesture.getTimeDifference(2);
1588     return (isMultiTouch ? (dt > (MININUM_GESTURE_DELAY_MILLISECONDS << 3) ? 0 :
1589       dragGesture.getSpeedPixelsPerMillisecond(2, 1))
1590       : (dt > MININUM_GESTURE_DELAY_MILLISECONDS ? 0 : dragGesture
1591         .getSpeedPixelsPerMillisecond(4, 2)));
1592   }
1593 
isRubberBandSelect(int action)1594   private boolean isRubberBandSelect(int action) {
1595     // drag and wheel and release
1596     action = action & ~Binding.DRAG | Binding.CLICK;
1597     return (rubberbandSelectionMode
1598         && bnd(action, ACTION_selectToggle, ACTION_selectOr, ACTION_selectAndNot));
1599   }
1600 
getRubberBand()1601   Rectangle getRubberBand() {
1602     return (rubberbandSelectionMode && rectRubber.x != Integer.MAX_VALUE ? rectRubber
1603         : null);
1604   }
1605 
calcRectRubberBand()1606   private void calcRectRubberBand() {
1607     int factor = (vwr.antialiased ? 2 : 1);
1608     if (current.x < pressed.x) {
1609       rectRubber.x = current.x * factor;
1610       rectRubber.width = (pressed.x - current.x) * factor;
1611     } else {
1612       rectRubber.x = pressed.x * factor;
1613       rectRubber.width = (current.x - pressed.x) * factor;
1614     }
1615     if (current.y < pressed.y) {
1616       rectRubber.y = current.y * factor;
1617       rectRubber.height = (pressed.y - current.y) * factor;
1618     } else {
1619       rectRubber.y = pressed.y * factor;
1620       rectRubber.height = (current.y - pressed.y) * factor;
1621     }
1622   }
1623 
1624   /**
1625    * Transform a screen pixel change to an angular change
1626    * such that a full sweep of the dimension (up to 500 pixels)
1627    * corresponds to 180 degrees of rotation.
1628    *
1629    * @param delta
1630    * @param isX
1631    * @return desired scaled rotation, in degrees
1632    */
getDegrees(float delta, boolean isX)1633   protected float getDegrees(float delta, boolean isX) {
1634     return delta / Math.min(500, isX ? vwr.getScreenWidth()
1635         : vwr.getScreenHeight()) * 180 * mouseDragFactor;
1636   }
1637 
isZoomArea(int x)1638   private boolean isZoomArea(int x) {
1639     return x > vwr.getScreenWidth() * (vwr.tm.stereoDoubleFull || vwr.tm.stereoDoubleDTI ? 2 : 1)
1640         * SLIDE_ZOOM_X_PERCENT / 100f;
1641   }
1642 
getPoint(Map<String, Object> t)1643   private Point3fi getPoint(Map<String, Object> t) {
1644     Point3fi pt = new Point3fi();
1645     pt.setT((P3) t.get("pt"));
1646     pt.mi = (short) ((Integer) t.get("modelIndex")).intValue();
1647     return pt;
1648   }
1649 
findNearestAtom(int x, int y, Point3fi nearestPoint, boolean isClicked)1650   private int findNearestAtom(int x, int y, Point3fi nearestPoint,
1651                               boolean isClicked) {
1652     int index = (drawMode || nearestPoint != null ? -1 : vwr
1653         .findNearestAtomIndexMovable(x, y, false));
1654     return (index >= 0 && (isClicked || mp == null)
1655         && !vwr.slm.isInSelectionSubset(index) ? -1 : index);
1656   }
1657 
isSelectAction(int action)1658   private boolean isSelectAction(int action) {
1659     return (bnd(action, ACTION_pickAtom)
1660         || !drawMode
1661         && !labelMode
1662         && apm == PICKING_IDENTIFY
1663         && bnd(action, ACTION_center)
1664         || dragSelectedMode
1665         && bnd(dragAction, ACTION_rotateSelected, ACTION_dragSelected)
1666         || bnd(action, ACTION_pickPoint, ACTION_selectToggle, ACTION_selectAndNot,
1667             ACTION_selectOr, ACTION_selectToggleExtended, ACTION_select));
1668   }
1669 
1670   //////////// specific actions ////////////////
1671 
1672   private MeasurementPending measurementQueued;
1673   public boolean zoomTrigger;
1674 
enterMeasurementMode(int iAtom)1675   private void enterMeasurementMode(int iAtom) {
1676     vwr.setPicked(iAtom, true);
1677     vwr.setCursor(GenericPlatform.CURSOR_CROSSHAIR);
1678     vwr.setPendingMeasurement(mp = getMP());
1679     measurementQueued = mp;
1680   }
1681 
getMP()1682   private MeasurementPending getMP() {
1683     return ((MeasurementPending) Interface
1684         .getInterface("org.jmol.modelset.MeasurementPending", vwr, "mouse")).set(vwr.ms);
1685   }
1686 
addToMeasurement(int atomIndex, Point3fi nearestPoint, boolean dblClick)1687   private int addToMeasurement(int atomIndex, Point3fi nearestPoint,
1688                                boolean dblClick) {
1689     if (atomIndex == -1 && nearestPoint == null || mp == null) {
1690       exitMeasurementMode(null);
1691       return 0;
1692     }
1693     int measurementCount = mp.count;
1694     if (mp.traceX != Integer.MIN_VALUE && measurementCount == 2)
1695       mp.setCount(measurementCount = 1);
1696     return (measurementCount == 4 && !dblClick ? measurementCount
1697         : mp.addPoint(atomIndex, nearestPoint, true));
1698   }
1699 
resetMeasurement()1700   private void resetMeasurement() {
1701     // doesn't reset the measurement that is being picked using
1702     // double-click, just the one using set picking measure.
1703     exitMeasurementMode(null);
1704     measurementQueued = getMP();
1705   }
1706 
exitMeasurementMode(String refreshWhy)1707   void exitMeasurementMode(String refreshWhy) {
1708     if (mp == null)
1709       return;
1710     vwr.setPendingMeasurement(mp = null);
1711     vwr.setCursor(GenericPlatform.CURSOR_DEFAULT);
1712     if (refreshWhy != null)
1713       vwr.refresh(Viewer.REFRESH_SYNC_MASK, refreshWhy);
1714   }
1715 
getSequence()1716   private void getSequence() {
1717     int a1 = measurementQueued.getAtomIndex(1);
1718     int a2 = measurementQueued.getAtomIndex(2);
1719     if (a1 < 0 || a2 < 0)
1720       return;
1721     try {
1722       String sequence = vwr.getSmilesOpt(null, a1, a2, JC.SMILES_GEN_BIO, null);
1723       vwr.setStatusMeasuring("measureSequence", -2, sequence, 0);
1724     } catch (Exception e) {
1725       Logger.error(e.toString());
1726     }
1727   }
1728 
minimize(boolean dragDone)1729   private void minimize(boolean dragDone) {
1730     int iAtom = dragAtomIndex;
1731     if (dragDone) {
1732       dragAtomIndex = -1;
1733       mkBondPressed = false;
1734     }
1735     vwr.dragMinimizeAtom(iAtom);
1736   }
1737 
queueAtom(int atomIndex, Point3fi ptClicked)1738   private int queueAtom(int atomIndex, Point3fi ptClicked) {
1739     int n = measurementQueued.addPoint(atomIndex, ptClicked, true);
1740     if (atomIndex >= 0)
1741       vwr.setStatusAtomPicked(atomIndex, "Atom #" + n + ":"
1742           + vwr.getAtomInfo(atomIndex), null, false);
1743     return n;
1744   }
1745 
setMotion(int cursor, boolean inMotion)1746   protected void setMotion(int cursor, boolean inMotion) {
1747     switch (vwr.currentCursor) {
1748     case GenericPlatform.CURSOR_WAIT:
1749       break;
1750     default:
1751       vwr.setCursor(cursor);
1752     }
1753     if (inMotion)
1754       vwr.setInMotion(true);
1755   }
1756 
zoomByFactor(int dz, int x, int y)1757   protected void zoomByFactor(int dz, int x, int y) {
1758     if (dz == 0)
1759       return;
1760     setMotion(GenericPlatform.CURSOR_ZOOM, true);
1761     vwr.zoomByFactor((float) Math.pow(mouseWheelFactor, dz), x, y);
1762     moved.setCurrent(current, 0);
1763     vwr.setInMotion(true);
1764     zoomTrigger = true;
1765     startHoverWatcher(true);
1766   }
1767 
1768 
1769   /// methods that utilize vwr.script
1770 
runScript(String script)1771   private void runScript(String script) {
1772     vwr.script(script);
1773   }
1774 
atomOrPointPicked(int atomIndex, Point3fi ptClicked)1775   private void atomOrPointPicked(int atomIndex, Point3fi ptClicked) {
1776     // atomIndex < 0 is off structure.
1777     // if picking spin or picking symmetry is on, then
1778     // we need to enter this method to process those events.
1779     if (atomIndex < 0) {
1780       resetMeasurement(); // for set picking measure only
1781       if (bnd(clickAction, ACTION_selectNone)) {
1782         runScript("select none");
1783         return;
1784       }
1785       if (apm != PICKING_SPIN
1786           && apm != PICKING_SYMMETRY)
1787         return;
1788     }
1789     int n = 2;
1790     switch (apm) {
1791     case PICKING_DRAG_ATOM:
1792       // this is done in mouse drag, not mouse release
1793     case PICKING_DRAG_MINIMIZE:
1794       return;
1795     case PICKING_OFF:
1796       return;
1797     case PICKING_STRUTS:
1798     case PICKING_CONNECT:
1799     case PICKING_DELETE_BOND:
1800       boolean isDelete = (apm == PICKING_DELETE_BOND);
1801       boolean isStruts = (apm == PICKING_STRUTS);
1802       if (!bnd(clickAction, (isDelete ? ACTION_deleteBond
1803           : ACTION_connectAtoms)))
1804         return;
1805       if (measurementQueued == null || measurementQueued.count == 0
1806           || measurementQueued.count > 2) {
1807         resetMeasurement();
1808         enterMeasurementMode(atomIndex);
1809       }
1810       addToMeasurement(atomIndex, ptClicked, true);
1811       if (queueAtom(atomIndex, ptClicked) != 2)
1812         return;
1813       String cAction = (isDelete
1814           || measurementQueued.isConnected(vwr.ms.at, 2) ? " DELETE"
1815           : isStruts ? "STRUTS" : "");
1816       runScript("connect " + measurementQueued.getMeasurementScript(" ", true)
1817           + cAction);
1818       resetMeasurement();
1819       return;
1820     case PICKING_MEASURE_TORSION:
1821       n++;
1822       //$FALL-THROUGH$
1823     case PICKING_MEASURE_ANGLE:
1824       n++;
1825       //$FALL-THROUGH$
1826     case PICKING_MEASURE:
1827     case PICKING_MEASURE_DISTANCE:
1828     case PICKING_MEASURE_SEQUENCE:
1829       if (!bnd(clickAction, ACTION_pickMeasure))
1830         return;
1831       if (measurementQueued == null || measurementQueued.count == 0
1832           || measurementQueued.count > n) {
1833         resetMeasurement();
1834         enterMeasurementMode(atomIndex);
1835       }
1836       addToMeasurement(atomIndex, ptClicked, true);
1837       queueAtom(atomIndex, ptClicked);
1838       int i = measurementQueued.count;
1839       if (i == 1)
1840         vwr.setPicked(atomIndex, true);
1841       if (i < n)
1842         return;
1843       if (apm == PICKING_MEASURE_SEQUENCE) {
1844         getSequence();
1845       } else {
1846         vwr.setStatusMeasuring("measurePicked", n, measurementQueued
1847             .getStringDetail(), measurementQueued.value);
1848         if (apm == PICKING_MEASURE
1849             || pickingStyleMeasure == PICKINGSTYLE_MEASURE_ON) {
1850           runScript("measure "
1851               + measurementQueued.getMeasurementScript(" ", true));
1852         }
1853       }
1854       resetMeasurement();
1855       return;
1856     }
1857     int mode = (mp != null
1858         && apm != PICKING_IDENTIFY ? PICKING_IDENTIFY
1859         : apm);
1860     switch (mode) {
1861     case PICKING_CENTER:
1862       if (!bnd(clickAction, ACTION_pickAtom))
1863         return;
1864       if (ptClicked == null) {
1865         zoomTo(atomIndex);
1866       } else {
1867         runScript("zoomTo " + Escape.eP(ptClicked));
1868       }
1869       return;
1870     case PICKING_SPIN:
1871     case PICKING_SYMMETRY:
1872       if (bnd(clickAction, ACTION_pickAtom))
1873         checkTwoAtomAction(ptClicked, atomIndex);
1874     }
1875     if (ptClicked != null)
1876       return;
1877     // atoms only here:
1878     BS bs;
1879     switch (mode) {
1880     case PICKING_IDENTIFY:
1881       if (!drawMode && !labelMode && bnd(clickAction, ACTION_center))
1882         zoomTo(atomIndex);
1883       else if (bnd(clickAction, ACTION_pickAtom))
1884         vwr.setStatusAtomPicked(atomIndex, null, null, false);
1885       return;
1886     case PICKING_LABEL:
1887       if (bnd(clickAction, ACTION_pickLabel)) {
1888         runScript("set labeltoggle {atomindex=" + atomIndex + "}");
1889         pickLabel(atomIndex);
1890       }
1891       return;
1892     case PICKING_INVERT_STEREO:
1893       if (bnd(clickAction, ACTION_assignNew)) {
1894         vwr.invertRingAt(atomIndex, true);
1895         vwr.setStatusAtomPicked(atomIndex, "invert stereo for atomIndex=" + atomIndex, null, false);
1896       }
1897       return;
1898     case PICKING_DELETE_ATOM:
1899       if (bnd(clickAction, ACTION_deleteAtom)) {
1900         bs = BSUtil.newAndSetBit(atomIndex);
1901         vwr.deleteAtoms(bs, false);
1902         vwr.setStatusAtomPicked(atomIndex, "deleted: " + Escape.eBS(bs), null, false);
1903       }
1904       return;
1905     }
1906     // set picking select options:
1907     String spec = "atomindex=" + atomIndex;
1908     switch (apm) {
1909     default:
1910       return;
1911     case PICKING_SELECT_ATOM:
1912       selectAtoms(spec);
1913       break;
1914     case PICKING_SELECT_GROUP:
1915       selectAtoms("within(group, " + spec + ")");
1916       break;
1917     case PICKING_SELECT_CHAIN:
1918       selectAtoms("within(chain, " + spec + ")");
1919       break;
1920     case PICKING_SELECT_POLYMER:
1921       selectAtoms("within(polymer, " + spec + ")");
1922       break;
1923     case PICKING_SELECT_STRUCTURE:
1924       selectAtoms("within(structure, " + spec + ")");
1925       break;
1926     case PICKING_SELECT_MOLECULE:
1927       selectAtoms("within(molecule, " + spec + ")");
1928       break;
1929     case PICKING_SELECT_MODEL:
1930       selectAtoms("within(model, " + spec + ")");
1931       break;
1932     // only the next two use VISIBLE (as per the documentation)
1933     case PICKING_SELECT_ELEMENT:
1934       selectAtoms("visible and within(element, " + spec + ")");
1935       break;
1936     case PICKING_SELECT_SITE:
1937       selectAtoms("visible and within(site, " + spec + ")");
1938       break;
1939     }
1940     vwr.clearClickCount();
1941     vwr.setStatusAtomPicked(atomIndex, null, null, false);
1942   }
1943 
assignNew(int x, int y)1944   private void assignNew(int x, int y) {
1945     if (!vwr.getModelkit(false).handleAssignNew(pressed, dragged, mp, dragAtomIndex)) {
1946       exitMeasurementMode("bond dropped");
1947     }
1948     exitMeasurementMode(null);
1949   }
1950 
bondPicked(int index)1951   private void bondPicked(int index) {
1952     if (bondPickingMode == PICKING_ASSIGN_BOND) {
1953       vwr.undoMoveActionClear(-1, T.save, true);
1954     }
1955 
1956     switch (bondPickingMode) {
1957     case PICKING_ASSIGN_BOND:
1958       vwr.setModelkitProperty("scriptAssignBond", Integer.valueOf(index));
1959       break;
1960     case PICKING_ROTATE_BOND:
1961       // done separately
1962       break;
1963     case PICKING_DELETE_BOND:
1964       vwr.deleteBonds(BSUtil.newAndSetBit(index));
1965     }
1966   }
1967 
checkTwoAtomAction(Point3fi ptClicked, int atomIndex)1968   private void checkTwoAtomAction(Point3fi ptClicked, int atomIndex) {
1969     boolean isSpin = (apm == PICKING_SPIN);
1970     if (vwr.tm.spinOn || vwr.tm.navOn
1971         || vwr.getPendingMeasurement() != null) {
1972       resetMeasurement();
1973       if (vwr.tm.spinOn)
1974         runScript("spin off");
1975       return;
1976     }
1977     if (measurementQueued.count >= 2)
1978       resetMeasurement();
1979     int queuedAtomCount = measurementQueued.count;
1980     if (queuedAtomCount == 1) {
1981       if (ptClicked == null) {
1982         if (measurementQueued.getAtomIndex(1) == atomIndex)
1983           return;
1984       } else {
1985         if (measurementQueued.getAtom(1).distance(ptClicked) == 0)
1986           return;
1987       }
1988     }
1989     if (atomIndex >= 0 || ptClicked != null)
1990       queuedAtomCount = queueAtom(atomIndex, ptClicked);
1991     if (queuedAtomCount < 2) {
1992       if (isSpin)
1993         vwr.scriptStatus(queuedAtomCount == 1 ? GT
1994             .$("pick one more atom in order to spin the model around an axis")
1995             : GT.$("pick two atoms in order to spin the model around an axis"));
1996       else
1997         vwr
1998             .scriptStatus(queuedAtomCount == 1 ? GT
1999                 .$("pick one more atom in order to display the symmetry relationship")
2000                 : GT
2001                     .$("pick two atoms in order to display the symmetry relationship between them"));
2002       return;
2003     }
2004     String s = measurementQueued.getMeasurementScript(" ", false);
2005     if (isSpin)
2006       runScript("spin" + s + " " + vwr.getInt(T.pickingspinrate));
2007     else
2008       runScript("draw symop " + s + ";show symop " + s);
2009   }
2010 
reset()2011   private void reset() {
2012     runScript("!reset");
2013   }
2014 
2015   private boolean selectionWorking = false;
2016 
selectAtoms(String item)2017   private void selectAtoms(String item) {
2018     if (mp != null || selectionWorking)
2019       return;
2020     selectionWorking = true;
2021     String s = (rubberbandSelectionMode
2022         || bnd(clickAction, ACTION_selectToggle) ? "selected and not ("
2023         + item + ") or (not selected) and " : bnd(clickAction,
2024         ACTION_selectAndNot) ? "selected and not " : bnd(clickAction,
2025         ACTION_selectOr) ? "selected or " : clickAction == 0
2026         || bnd(clickAction, ACTION_selectToggleExtended) ? "selected tog "
2027         : bnd(clickAction, ACTION_select) ? "" : null);
2028     if (s != null) {
2029       s += "(" + item + ")";
2030       try {
2031         BS bs = vwr.getAtomBitSetEval(null, s);
2032         setAtomsPicked(bs, "selected: " + Escape.eBS(bs));
2033         vwr.refresh(Viewer.REFRESH_SYNC_MASK, "selections set");
2034       } catch (Exception e) {
2035         // ignore
2036       }
2037     }
2038     selectionWorking = false;
2039   }
2040 
setAtomsPicked(BS bs, String msg)2041   private void setAtomsPicked(BS bs, String msg) {
2042     vwr.select(bs, false, 0, false);
2043     vwr.setStatusAtomPicked(-1, msg, null, false);
2044   }
2045 
selectRb(int action)2046   private void selectRb(int action) {
2047     BS bs = vwr.ms.findAtomsInRectangle(rectRubber);
2048     if (bs.length() > 0) {
2049       String s = Escape.eBS(bs);
2050       if (bnd(action, ACTION_selectOr))
2051         runScript("selectionHalos on;select selected or " + s);
2052       else if (bnd(action, ACTION_selectAndNot))
2053         runScript("selectionHalos on;select selected and not " + s);
2054       else
2055         // ACTION_selectToggle
2056         runScript("selectionHalos on;select selected tog " + s);
2057     }
2058     vwr.refresh(Viewer.REFRESH_SYNC_MASK, "mouseReleased");
2059   }
2060 
toggleMeasurement()2061   private void toggleMeasurement() {
2062     if (mp == null)
2063       return;
2064     int measurementCount = mp.count;
2065     if (measurementCount >= 2 && measurementCount <= 4)
2066       runScript("!measure "
2067           + mp.getMeasurementScript(" ", true));
2068     exitMeasurementMode(null);
2069   }
2070 
zoomTo(int atomIndex)2071   private void zoomTo(int atomIndex) {
2072     runScript("zoomTo (atomindex=" + atomIndex + ")");
2073     vwr.setStatusAtomPicked(atomIndex, null, null, false);
2074   }
2075 
userActionEnabled(int action)2076   public boolean userActionEnabled(int action) {
2077     return vwr.isFunction(getActionName(action).toLowerCase());
2078   }
2079 
2080   /**
2081    * If the user has created a function to handle this action,
2082    * run it and cancel action processing if that function returns an explicit FALSE;
2083    *
2084    * @param action
2085    * @param params
2086    * @return true to continue with the standard action
2087    */
userAction(int action, Object[] params)2088   public boolean userAction(int action, Object[] params) {
2089     if (!userActionEnabled(action))
2090         return false;
2091     SV result = ScriptEval.runUserAction(getActionName(action), params, vwr);
2092     return !SV.vF.equals(result);
2093   }
2094 
2095 }
2096 
2097 class MotionPoint {
2098   int index;
2099   int x;
2100   int y;
2101   long time;
2102 
set(int index, int x, int y, long time)2103   void set(int index, int x, int y, long time) {
2104     this.index = index;
2105     this.x = x;
2106     this.y = y;
2107     this.time = time;
2108   }
2109 
2110   @Override
toString()2111   public String toString() {
2112     return "[x = " + x + " y = " + y + " time = " + time + " ]";
2113   }
2114 }
2115 
2116 class Gesture {
2117   private int action;
2118   MotionPoint[] nodes;
2119   private int ptNext;
2120   private long time0;
2121   private Viewer vwr;
2122 
Gesture(int nPoints, Viewer vwr)2123   public Gesture(int nPoints, Viewer vwr) {
2124     this.vwr = vwr;
2125     nodes = new MotionPoint[nPoints];
2126     for (int i = 0; i < nPoints; i++)
2127       nodes[i] = new MotionPoint();
2128   }
2129 
setAction(int action, long time)2130   void setAction(int action, long time) {
2131     this.action = action;
2132     ptNext = 0;
2133     time0 = time;
2134     for (int i = 0; i < nodes.length; i++)
2135       nodes[i].index = -1;
2136   }
2137 
add(int action, int x, int y, long time)2138   int add(int action, int x, int y, long time) {
2139     this.action = action;
2140     getNode(ptNext).set(ptNext, x, y, time - time0);
2141     ptNext++;
2142     return ptNext;
2143   }
2144 
getTimeDifference(int nPoints)2145   public long getTimeDifference(int nPoints) {
2146     nPoints = getPointCount2(nPoints, 0);
2147     if (nPoints < 2)
2148       return 0;
2149     MotionPoint mp1 = getNode(ptNext - 1);
2150     MotionPoint mp0 = getNode(ptNext - nPoints);
2151     return mp1.time - mp0.time;
2152   }
2153 
getSpeedPixelsPerMillisecond(int nPoints, int nPointsPrevious)2154   public float getSpeedPixelsPerMillisecond(int nPoints, int nPointsPrevious) {
2155     nPoints = getPointCount2(nPoints, nPointsPrevious);
2156     if (nPoints < 2)
2157       return 0;
2158     MotionPoint mp1 = getNode(ptNext - 1 - nPointsPrevious);
2159     MotionPoint mp0 = getNode(ptNext - nPoints - nPointsPrevious);
2160     float dx = ((float) (mp1.x - mp0.x)) / vwr.getScreenWidth() * 360;
2161     float dy = ((float) (mp1.y - mp0.y)) / vwr.getScreenHeight() * 360;
2162     return (float) Math.sqrt(dx * dx + dy * dy) / (mp1.time - mp0.time);
2163   }
2164 
getDX(int nPoints, int nPointsPrevious)2165   int getDX(int nPoints, int nPointsPrevious) {
2166     nPoints = getPointCount2(nPoints, nPointsPrevious);
2167     if (nPoints < 2)
2168       return 0;
2169     MotionPoint mp1 = getNode(ptNext - 1 - nPointsPrevious);
2170     MotionPoint mp0 = getNode(ptNext - nPoints - nPointsPrevious);
2171     return mp1.x - mp0.x;
2172   }
2173 
getDY(int nPoints, int nPointsPrevious)2174   int getDY(int nPoints, int nPointsPrevious) {
2175     nPoints = getPointCount2(nPoints, nPointsPrevious);
2176     if (nPoints < 2)
2177       return 0;
2178     MotionPoint mp1 = getNode(ptNext - 1 - nPointsPrevious);
2179     MotionPoint mp0 = getNode(ptNext - nPoints - nPointsPrevious);
2180     return mp1.y - mp0.y;
2181   }
2182 
getPointCount()2183   int getPointCount() {
2184     return ptNext;
2185   }
2186 
getPointCount2(int nPoints, int nPointsPrevious)2187   private int getPointCount2(int nPoints, int nPointsPrevious) {
2188     if (nPoints > nodes.length - nPointsPrevious)
2189       nPoints = nodes.length - nPointsPrevious;
2190     int n = nPoints + 1;
2191     for (; --n >= 0;)
2192       if (getNode(ptNext - n - nPointsPrevious).index >= 0)
2193         break;
2194     return n;
2195   }
2196 
getNode(int i)2197   MotionPoint getNode(int i) {
2198     return nodes[(i + nodes.length + nodes.length) % nodes.length];
2199   }
2200 
2201   @Override
toString()2202   public String toString() {
2203     if (nodes.length == 0)
2204       return "" + this;
2205     return Binding.getMouseActionName(action, false) + " nPoints = " + ptNext
2206         + " " + nodes[0];
2207   }
2208 }
2209 
2210