1 /* $RCSfile$
2  * $Author: hansonr $
3  * $Date: 2010-05-11 15:47:18 -0500 (Tue, 11 May 2010) $
4  * $Revision: 13064 $
5  *
6  * Copyright (C) 2000-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.modelkit;
25 
26 import java.util.Hashtable;
27 import java.util.Map;
28 
29 import org.jmol.api.SC;
30 import org.jmol.i18n.GT;
31 import org.jmol.modelset.Atom;
32 import org.jmol.modelset.AtomCollection;
33 import org.jmol.modelset.Bond;
34 import org.jmol.modelset.MeasurementPending;
35 import org.jmol.modelset.ModelSet;
36 import org.jmol.popup.JmolGenericPopup;
37 import org.jmol.popup.PopupResource;
38 import org.jmol.script.ScriptEval;
39 import org.jmol.script.T;
40 import org.jmol.util.BSUtil;
41 import org.jmol.util.Edge;
42 import org.jmol.util.Elements;
43 import org.jmol.util.Escape;
44 import org.jmol.util.Logger;
45 import org.jmol.viewer.ActionManager;
46 import org.jmol.viewer.JC;
47 import org.jmol.viewer.MouseState;
48 import org.jmol.viewer.Viewer;
49 
50 import javajs.util.AU;
51 import javajs.util.BS;
52 import javajs.util.Lst;
53 import javajs.util.P3;
54 import javajs.util.PT;
55 import javajs.util.SB;
56 import javajs.util.V3;
57 
58 /**
59  * An abstract popup class that is instantiated for a given platform and context
60  * as one of:
61  *
62  * <pre>
63  *   -- abstract ModelKitPopup
64  *      -- AwtModelKitPopup
65  *      -- JSModelKitPopup
66  * </pre>
67  *
68  */
69 
70 abstract public class ModelKitPopup extends JmolGenericPopup {
71 
menuHidePopup(SC popup)72   abstract protected void menuHidePopup(SC popup);
73 
74   private static PopupResource bundle = new ModelKitPopupResourceBundle(null, null);
75 
76   // scripting options
77 
78   //  { "xtalModeMenu", "mkmode_molecular mkmode_view mkmode_edit" },
79   public static final String MODE_OPTIONS = ";view;edit;molecular;";
80   public static final String SYMMETRY_OPTIONS = ";none;applylocal;retainlocal;applyfull;";
81   public static final String UNITCELL_OPTIONS = ";packed;extend;";
82   public static final String BOOLEAN_OPTIONS = ";autobond;hidden;showsymopinfo;clicktosetelement;addhydrogen;addhydrogens;";
83   public static final String SET_OPTIONS = ";element;";
84 
85   ////////////// modelkit state //////////////
86 
87   private static final int MAX_LABEL = 32;
88   static final String ATOM_MENU = "atomMenu";
89   static final String BOND_MENU = "bondMenu";
90   static final String XTAL_MENU = "xtalMenu";
91 
92   public final static int STATE_BITS_XTAL /* 0b00000000011*/ = 0x03;
93   public final static int STATE_MOLECULAR /* 0b00000000000*/ = 0x00;
94   public final static int STATE_XTALVIEW /* 0b00000000001*/ = 0x01;
95   public final static int STATE_XTALEDIT /* 0b00000000010*/ = 0x02;
96 
97   public final static int STATE_BITS_SYM_VIEW /* 0b00000011100*/ = 0x1c;
98   public final static int STATE_SYM_NONE      /* 0b00000000000*/ = 0x00;
99   public final static int STATE_SYM_SHOW      /* 0b00000001000*/ = 0x08;
100 
101   public final static int STATE_BITS_SYM_EDIT   /* 0b00011100000*/ = 0xe0;
102   public final static int STATE_SYM_APPLYLOCAL  /* 0b00000100000*/ = 0x20;
103   public final static int STATE_SYM_RETAINLOCAL /* 0b00001000000*/ = 0x40;
104   public final static int STATE_SYM_APPLYFULL   /* 0b00010000000*/ = 0x80;
105 
106   public final static int STATE_BITS_UNITCELL   /* 0b11100000000*/ = 0x700;
107   public final static int STATE_UNITCELL_PACKED /* 0b00000000000*/ = 0x000;
108   public final static int STATE_UNITCELL_EXTEND /* 0b00100000000*/ = 0x100;
109 
110   private static final P3 Pt000 = new P3();
111 
112   /**
113    * set in assignAtom only
114    */
115   private boolean allowPopup = true;
116 
117   /**
118    * set by MODELKIT [DISPLAY/HIDE]
119    */
120   private boolean hidden = false;
121 
isHidden()122   public boolean isHidden() {
123     return hidden;
124   }
125 
126   private boolean hasUnitCell;
127   private String[] allOperators;
128   private int currentModelIndex = -1;
129 
130   private boolean alertedNoEdit;
131 
132   private String atomHoverLabel = "C", bondHoverLabel = GT.$("increase order"), xtalHoverLabel;
133   private String activeMenu;
134   protected ModelSet lastModelSet;
135 
136   private String pickAtomAssignType = "C";
137   private String lastElementType = "C";
138   private char pickBondAssignType = 'p'; // increment up
139   private boolean isPickAtomAssignCharge; // pl or mi
140 
141   private BS bsHighlight = new BS();
142 
143   private int bondIndex = -1, bondAtomIndex1 = -1, bondAtomIndex2 = -1;
144 
145   private BS bsRotateBranch;
146   private int branchAtomIndex;
147   private boolean isRotateBond;
148 
149   private int[] screenXY = new int[2]; // for tracking mouse-down on bond
150 
151   private Map<String, Object> mkdata = new Hashtable<String, Object>();
152 
153 
154 
155   private boolean showSymopInfo = true;
156 
157   /**
158    * when TRUE, add H atoms to C when added to the modelSet.
159    */
160   private boolean addXtalHydrogens = true;
161 
162   /**
163    * Except for H atoms, do not allow changes to elements just by clicking them.
164    * This protects against doing that inadvertently when editing.
165    *
166    */
167   private boolean clickToSetElement = true;
168 
169   /**
170    * set to true for proximity-based autobonding (prior to 14.32.4/15.2.4 the default was TRUE
171    */
172   private boolean autoBond = false;
173 
174 
175   private P3 centerPoint, spherePoint, viewOffset;
176   private float centerDistance;
177   private Object symop;
178   private int centerAtomIndex = -1, secondAtomIndex = -1, atomIndexSphere = -1;
179   private String drawData;
180   private String drawScript;
181   private int iatom0;
182 
183   protected SC bondRotationCheckBox, prevBondCheckBox;
184 
185   private String bondRotationName = ".modelkitMenu.bondMenu.rotateBondP!RD";
186 
187   private String lastCenter = "0 0 0", lastOffset = "0 0 0";
188 
189 
ModelKitPopup()190   public ModelKitPopup() {
191   }
192 
193   @Override
initialize(Viewer vwr, PopupResource bundle, String title)194   protected void initialize(Viewer vwr, PopupResource bundle, String title) {
195    super.initialize(vwr, bundle, title);
196    initializeForModel();
197   }
198 
199   //////////////// menu creation and update ///////////////
200 
201   @Override
getBundle(String menu)202   protected PopupResource getBundle(String menu) {
203     return bundle;
204   }
205 
initializeForModel()206   public void initializeForModel() {
207     resetBondFields("init");
208     allOperators = null;
209     currentModelIndex = -999;
210     iatom0 = 0;
211     atomIndexSphere = centerAtomIndex = secondAtomIndex = -1;
212     centerPoint = spherePoint = null;
213    // no! atomHoverLabel = bondHoverLabel = xtalHoverLabel = null;
214     hasUnitCell = (vwr.getCurrentUnitCell() != null);
215     symop = null;
216     setDefaultState(hasUnitCell ? STATE_XTALVIEW : STATE_MOLECULAR);
217     //setProperty("clicktosetelement",Boolean.valueOf(!hasUnitCell));
218     //setProperty("addhydrogen",Boolean.valueOf(!hasUnitCell));
219   }
220 
221   @Override
jpiShow(int x, int y)222   public void jpiShow(int x, int y) {
223     if (!hidden && allowPopup)
224       super.jpiShow(x, y);
225   }
226 
227   @Override
jpiUpdateComputedMenus()228   public void jpiUpdateComputedMenus() {
229     hasUnitCell = (vwr.getCurrentUnitCell() != null);
230     htMenus.get(XTAL_MENU).setEnabled(hasUnitCell);
231     boolean isOK = true;
232     if (vwr.ms != lastModelSet) {
233       lastModelSet = vwr.ms;
234       isOK = false;
235 //    } else if (currentModelIndex == -1 || currentModelIndex != vwr.am.cmi) {
236 //      isOK = false;
237     }
238     currentModelIndex = Math.max(vwr.am.cmi, 0);
239     iatom0 = vwr.ms.am[currentModelIndex].firstAtomIndex;
240     if (!isOK) {
241       allOperators = null;
242       updateOperatorMenu();
243     }
244     updateAllXtalMenuOptions();
245 }
246 
247   @Override
appUpdateForShow()248   protected void appUpdateForShow() {
249     jpiUpdateComputedMenus();
250   }
251 
updateOperatorMenu()252   protected void updateOperatorMenu() {
253     if (allOperators != null)
254       return;
255     String data = runScriptBuffered("show symop");
256     allOperators = PT.split(data.trim().replace('\t', ' '), "\n");
257     SC menu = htMenus.get("xtalOp!PersistMenu");
258     if (menu != null)
259       addAllCheckboxItems(menu, allOperators);
260   }
261 
addAllCheckboxItems(SC menu, String[] labels)262   private void addAllCheckboxItems(SC menu, String[] labels) {
263     menuRemoveAll(menu, 0);
264     SC subMenu = menu;
265     int pt = (labels.length > MAX_LABEL ? 0 : Integer.MIN_VALUE);
266     for (int i = 0; i < labels.length; i++) {
267       if (pt >= 0 && (pt++ % MAX_LABEL) == 0) {
268         String id = "mtsymop" + pt + "Menu";
269         subMenu = menuNewSubMenu(
270             (i + 1) + "..." + Math.min(i + MAX_LABEL, labels.length),
271             menuGetId(menu) + "." + id);
272         menuAddSubMenu(menu, subMenu);
273         htMenus.put(id, subMenu);
274         pt = 1;
275       }
276       if (i == 0)
277         menuEnable(
278             menuCreateItem(subMenu, GT.$("none"), "draw sym_* delete", null),
279             true);
280       String sym = labels[i]; // XYZoriginal
281       menuEnable(menuCreateItem(subMenu, sym, sym,
282           subMenu.getName() + "." + "mkop_" + (i + 1)), true);
283     }
284 
285   }
286 
updateAllXtalMenuOptions()287   protected void updateAllXtalMenuOptions() {
288 
289     //    "mkaddHydrogens??P!CB", "add hydrogens on new atoms",
290     //    "mkclicktosetelement??P!CB", "allow clicking to set atom element",
291     //    "mksel_atom", "select atom",
292     //    "mksel_position", "select position",
293     //    "mkmode_molecular", GT.$("No View/Edit"),
294     //    "mksymmetry_none", GT.$("do not apply"),
295     //    "mksymmetry_retainLocal", GT.$("retain local"),
296     //    "mksymmetry_applyLocal", GT.$("apply local"),
297     //    "mksymmetry_applyFull", GT.$("apply full"),
298     //    "mkunitcell_extend", GT.$("extend cell"),
299     //    "mkunitcell_packed", GT.$("pack cell"),
300     //    "mkasymmetricUnit", GT.$("asymmetric unit"),
301     //    "mkallAtoms", GT.$("all atoms"),
302 
303     // mode
304     String text = "";
305     switch (getMKState()) {
306     case STATE_MOLECULAR:
307       text = " (not enabled)";
308       break;
309     case STATE_XTALVIEW:
310       text = " (view)";
311       break;
312     case STATE_XTALEDIT:
313       text = " (edit)";
314       break;
315     }
316     setLabel("xtalModePersistMenu", "Crystal Mode: " + text);
317 
318     // atom or position
319     text = (centerAtomIndex < 0 && centerPoint == null ? "(not selected)"
320         : centerAtomIndex >= 0 ? vwr.getAtomInfo(centerAtomIndex) : centerPoint.toString());
321     setLabel("xtalSelPersistMenu", "Center: " + text);
322     // operator
323     text = (symop == null || allOperators == null ? "(no operator selected)" : symop instanceof Integer ? allOperators[((Integer) symop).intValue() - 1] : symop.toString());
324     setLabel("operator", text);
325 
326     // editing option
327     switch (getSymEditState()) {
328     case STATE_SYM_NONE:
329       text = "do not apply symmetry";
330       break;
331     case STATE_SYM_RETAINLOCAL:
332       text = "retain local symmetry";
333       break;
334     case STATE_SYM_APPLYLOCAL:
335       text = "apply local symmetry";
336       break;
337     case STATE_SYM_APPLYFULL:
338       text = "apply full symmetry";
339       break;
340     }
341     setLabel("xtalEditOptPersistMenu", "Edit option: " + text);
342 
343     // packing
344     switch (getUnitCellState()) {
345     case STATE_UNITCELL_PACKED:
346       text = "packed";
347       break;
348     case STATE_UNITCELL_EXTEND:
349       text = "unpacked" + (viewOffset == null ? "(no view offset)" : "(view offset=" + viewOffset + ")");
350       break;
351     }
352     setLabel("xtalPackingPersistMenu", "Packing: " + text);
353 
354   }
355 
setLabel(String key, String label)356   private void setLabel(String key, String label) {
357     menuSetLabel(htMenus.get(key), label);
358   }
359 
360   /**
361    * for FrankRender -- the thin box on the top left
362    *
363    * @return [ "atomMenu" | "bondMenu" | "xtalMenu" | null ]
364    */
getActiveMenu()365   public String getActiveMenu() {
366     return activeMenu;
367   }
368 
369   /**
370    * Set the active menu and request a repaint.
371    *
372    * @param name
373    * @return activeMenu or null
374    */
setActiveMenu(String name)375   public String setActiveMenu(String name) {
376     // TODO -- if the hovering is working, this should not be necessary
377     String active = (name.indexOf(XTAL_MENU) >= 0 ? XTAL_MENU
378         : name.indexOf(ATOM_MENU) >= 0 ? ATOM_MENU
379             : name.indexOf(BOND_MENU) >= 0 ? BOND_MENU : null);
380     if (active != null) {
381       activeMenu = active;
382       if ((active == XTAL_MENU) == (getMKState() == STATE_MOLECULAR))
383         setMKState(active == XTAL_MENU ? STATE_XTALVIEW : STATE_MOLECULAR);
384       vwr.refresh(Viewer.REFRESH_REPAINT, "modelkit");
385       if (active == BOND_MENU && prevBondCheckBox == null)
386         prevBondCheckBox = htMenus.get("assignBond_pP!RD");
387     }
388 //    System.out.println("active menu is " + activeMenu + " state=" + getMKState());
389     return active;
390   }
391 
392   /**
393    * Set the active menu based on updating a value -- usually by the user, but
394    * also during setup (ignored).
395    *
396    */
397   @Override
appUpdateSpecialCheckBoxValue(SC source, String actionCommand, boolean selected)398   protected void appUpdateSpecialCheckBoxValue(SC source, String actionCommand,
399                                                boolean selected) {
400     if (!selected)
401       return;
402     String name = source.getName();
403     if (!updatingForShow && setActiveMenu(name) != null) {
404       exitBondRotation();
405       String text = source.getText();
406       // don't turn this into a Java 8 switch -- we need this to still compile in Java 6 for legacy Jmol
407       if (activeMenu == BOND_MENU) {
408         bondHoverLabel = text;
409         if (name.equals(bondRotationName )) {
410           bondRotationCheckBox = source;
411         } else {
412           prevBondCheckBox = source;
413         }
414       } else if (activeMenu == ATOM_MENU) {
415         atomHoverLabel = text;
416       } else if (activeMenu == XTAL_MENU) {
417         xtalHoverLabel = atomHoverLabel = text;
418       }
419     }
420   }
421 
422   //  { "xtalSelMenu", "mksel_atom mksel_position" },
423   //  { "xtalSelOpMenu", "mkselop_byop xtalOpMenu mkselop_addOffset mkselop_atom2" },
424 
425   private int state = STATE_MOLECULAR & STATE_SYM_NONE & STATE_SYM_APPLYFULL
426       & STATE_UNITCELL_EXTEND; // 0x00
427   private float rotationDeg;
428 
isXtalState()429   private boolean isXtalState() {
430     return ((state & STATE_BITS_XTAL) != 0);
431   }
432 
setMKState(int bits)433   private void setMKState(int bits) {
434     state = (state & ~STATE_BITS_XTAL) | (hasUnitCell ? bits : STATE_MOLECULAR);
435   }
436 
getMKState()437   private int getMKState() {
438     return state & STATE_BITS_XTAL;
439   }
440 
setSymEdit(int bits)441   private void setSymEdit(int bits) {
442     state = (state & ~STATE_BITS_SYM_EDIT) | bits;
443   }
444 
getSymEditState()445   private int getSymEditState() {
446     return state & STATE_BITS_SYM_EDIT;
447   }
448 
setSymViewState(int bits)449   private void setSymViewState(int bits) {
450     state = (state & ~STATE_BITS_SYM_VIEW) | bits;
451   }
452 
getSymViewState()453   private int getSymViewState() {
454     return state & STATE_BITS_SYM_VIEW;
455   }
456 
setUnitCell(int bits)457   private void setUnitCell(int bits) {
458     state = (state & ~STATE_BITS_UNITCELL) | bits;
459   }
460 
getUnitCellState()461   private int getUnitCellState() {
462     return state & STATE_BITS_UNITCELL;
463   }
464 
isPickAtomAssignCharge()465   public boolean isPickAtomAssignCharge() {
466     return isPickAtomAssignCharge;
467   }
468 
469   /** Get a property of the modelkit.
470    *
471    * @param data a name or an array with [name, value]
472    * @return value
473    */
getProperty(Object data)474   public Object getProperty(Object data) {
475     String key = (data instanceof String ? data : ((Object[]) data)[0]).toString();
476     Object value = (data instanceof String ? null : ((Object[]) data)[1]);
477     return setProperty(key, value);
478   }
479 
480   /**
481    * Modify the state by setting a property -- primarily from CmdExt.modelkit.
482    *
483    * Also can be used for "get" purposes.
484    *
485    * @param name
486    * @param value
487    * @return null or "get" value
488    */
setProperty(String name, Object value)489   public synchronized Object setProperty(String name, Object value) {
490     try {
491       name = name.toLowerCase().intern();
492       //    if (value != null)
493       //      System.out.println("ModelKitPopup.setProperty " + name + "=" + value);
494 
495       // boolean get/set
496 
497       if (name == "addhydrogen" || name == "addhydrogens") {
498         if (value != null)
499           addXtalHydrogens = isTrue(value);
500         return Boolean.valueOf(addXtalHydrogens);
501       }
502 
503       if (name == "autobond") {
504         if (value != null)
505           autoBond = isTrue(value);
506         return Boolean.valueOf(autoBond);
507       }
508 
509       if (name == "clicktosetelement") {
510         if (value != null)
511           clickToSetElement = isTrue(value);
512         return Boolean.valueOf(clickToSetElement);
513       }
514 
515       if (name == "hidden") {
516         if (value != null)
517           hidden = isTrue(value);
518         return Boolean.valueOf(hidden);
519       }
520 
521       if (name == "ismolecular") {
522         return Boolean.valueOf(getMKState() == STATE_MOLECULAR);
523       }
524 
525       if (name == "showsymopinfo") {
526         if (value != null)
527           showSymopInfo = isTrue(value);
528         return Boolean.valueOf(showSymopInfo);
529       }
530 
531       // get only
532 
533       if (name == "hoverlabel") {
534         // no setting of this, only getting
535         return getHoverLabel(((Integer) value).intValue());
536       }
537 
538       if (name == "alloperators") {
539         return allOperators;
540       }
541 
542       if (name == "data") {
543         return getData(value == null ? null : value.toString());
544       }
545 
546       if (name == "invariant") {
547         int iatom = (value instanceof BS ? ((BS) value).nextSetBit(0) : -1);
548         P3 atom = vwr.ms.getAtom(iatom);
549         if (atom == null)
550           return null;
551         return vwr.getSymmetryInfo(iatom, null, -1, null, atom, atom, T.array,
552             null, 0, 0, 0);
553       }
554 
555       if (name == "symop") {
556         setDefaultState(STATE_XTALVIEW);
557         if (value != null) {
558           symop = value;
559           showSymop(symop);
560         }
561         return symop;
562       }
563 
564       if (name == "atomtype") {
565         if (value != null) {
566           pickAtomAssignType = (String) value;
567           isPickAtomAssignCharge = (pickAtomAssignType.equals("pl")
568               || pickAtomAssignType.equals("mi"));
569           if (!isPickAtomAssignCharge && !"X".equals(pickAtomAssignType)) {
570             lastElementType = pickAtomAssignType;
571           }
572         }
573         return pickAtomAssignType;
574       }
575 
576       if (name == "bondtype") {
577         if (value != null) {
578           String s = ((String) value).substring(0, 1).toLowerCase();
579           if (" 012345pm".indexOf(s) > 0)
580             pickBondAssignType = s.charAt(0);
581         }
582         return "" + pickBondAssignType;
583       }
584 
585       if (name == "bondindex") {
586         if (value != null) {
587           setBondIndex(((Integer) value).intValue(), false);
588         }
589         return (bondIndex < 0 ? null : Integer.valueOf(bondIndex));
590       }
591 
592       if (name == "rotatebondindex") {
593         if (value != null) {
594           setBondIndex(((Integer) value).intValue(), true);
595         }
596         return (bondIndex < 0 ? null : Integer.valueOf(bondIndex));
597       }
598 
599       if (name == "offset") {
600         if (value == "none") {
601           viewOffset = null;
602         } else if (value != null) {
603           viewOffset = (value instanceof P3 ? (P3) value
604               : pointFromTriad(value.toString()));
605           if (viewOffset != null)
606             setSymViewState(STATE_SYM_SHOW);
607         }
608         showXtalSymmetry();
609         return viewOffset;
610       }
611 
612       if (name == "distance") {
613         setDefaultState(STATE_XTALEDIT);
614         float d = (value == null ? Float.NaN
615             : value instanceof Float ? ((Float) value).floatValue()
616                 : PT.parseFloat((String) value));
617         if (!Float.isNaN(d)) {
618           notImplemented("setProperty: distance");
619           centerDistance = d;
620         }
621         return Float.valueOf(centerDistance);
622       }
623 
624       if (name == "point") {
625         if (value != null) {
626           notImplemented("setProperty: point");
627           setDefaultState(STATE_XTALEDIT);
628           spherePoint = (P3) value;
629           atomIndexSphere = (spherePoint instanceof Atom
630               ? ((Atom) spherePoint).i
631               : -1);
632         }
633         return spherePoint;
634       }
635 
636       if (name == "screenxy") {
637         if (value != null) {
638           screenXY = (int[]) value;
639         }
640         return screenXY;
641       }
642 
643       // set only (always returning null):
644 
645       if (name == "bondatomindex") {
646         int i = ((Integer) value).intValue();
647         if (i != bondAtomIndex2)
648           bondAtomIndex1 = i;
649 
650         bsRotateBranch = null;
651         return null;
652       }
653 
654       if (name == "highlight") {
655         if (value == null)
656           bsHighlight = new BS();
657         else
658           bsHighlight = (BS) value;
659         return null;
660       }
661       if (name == "mode") { // view, edit, or molecular
662         boolean isEdit = ("edit".equals(value));
663         setMKState("view".equals(value) ? STATE_XTALVIEW
664             : isEdit ? STATE_XTALEDIT : STATE_MOLECULAR);
665         if (isEdit)
666           addXtalHydrogens = false;
667         return null;
668       }
669 
670       if (name == "symmetry") {
671         setDefaultState(STATE_XTALEDIT);
672         name = ((String) value).toLowerCase().intern();
673         setSymEdit(name == "applylocal" ? STATE_SYM_APPLYLOCAL
674             : name == "retainlocal" ? STATE_SYM_RETAINLOCAL
675                 : name == "applyfull" ? STATE_SYM_APPLYFULL : 0);
676         showXtalSymmetry();
677         return null;
678       }
679 
680       if (name == "unitcell") { // packed or extend
681         boolean isPacked = "packed".equals(value);
682         setUnitCell(isPacked ? STATE_UNITCELL_PACKED : STATE_UNITCELL_EXTEND);
683         viewOffset = (isPacked ? Pt000 : null);
684         return null;
685       }
686 
687       if (name == "center") {
688         setDefaultState(STATE_XTALVIEW);
689         centerPoint = (P3) value;
690         lastCenter = centerPoint.x + " " + centerPoint.y + " " + centerPoint.z;
691         centerAtomIndex = (centerPoint instanceof Atom ? ((Atom) centerPoint).i
692             : -1);
693         atomIndexSphere = -1;
694         secondAtomIndex = -1;
695         processAtomClick(centerAtomIndex);
696         return null;
697       }
698 
699       if (name == "scriptassignbond") {
700         // from ActionManger only
701         appRunScript("modelkit assign bond [{" + value + "}] \""
702             + pickBondAssignType + "\"");
703         return null;
704       }
705 
706       // not yet implemented
707       if (name == "addconstraint") {
708         notImplemented("setProperty: addConstraint");
709       }
710 
711       if (name == "removeconstraint") {
712         notImplemented("setProperty: removeConstraint");
713       }
714 
715       if (name == "removeallconstraints") {
716         notImplemented("setProperty: removeAllConstraints");
717       }
718 
719       System.err.println("ModelKitPopup.setProperty? " + name + " " + value);
720 
721     } catch (Exception e) {
722       return "?";
723     }
724 
725     return null;
726   }
727 
isTrue(Object value)728   private static boolean isTrue(Object value) {
729     return (Boolean.valueOf(value.toString()) == Boolean.TRUE);
730   }
731 
732   /**
733    * @param key
734    * @return
735    */
736   @SuppressWarnings("javadoc")
getData(String key)737   private Object getData(String key) {
738     addData("addHydrogens", Boolean.valueOf(addXtalHydrogens));
739     addData("autobond", Boolean.valueOf(autoBond));
740     addData("clickToSetElement", Boolean.valueOf(clickToSetElement));
741     addData("hidden", Boolean.valueOf(hidden));
742     addData("showSymopInfo", Boolean.valueOf(showSymopInfo));
743     addData("centerPoint" , centerPoint);
744     addData("centerAtomIndex", Integer.valueOf(centerAtomIndex));
745     addData("secondAtomIndex", Integer.valueOf(secondAtomIndex));
746     addData("symop",  symop);
747     addData("offset",  viewOffset);
748     addData("drawData", drawData);
749     addData("drawScript", drawScript);
750     addData("isMolecular", Boolean.valueOf(getMKState() == STATE_MOLECULAR));
751     return mkdata;
752   }
753 
addData(String key, Object value)754   private void addData(String key, Object value) {
755     mkdata.put(key, value == null ? "null" : value);
756   }
757 
758   /**
759    * An atom has been clicked -- handle it. Called from CmdExt.assignAtom
760    * from the script created in ActionManager.assignNew from Actionmanager.checkReleaseAction
761    *
762    * @param atomIndex
763    * @return true if handled
764    */
processAtomClick(int atomIndex)765   private boolean processAtomClick(int atomIndex) {
766     switch (getMKState()) {
767     case STATE_MOLECULAR:
768       return isVwrRotateBond();
769     case STATE_XTALVIEW:
770       centerAtomIndex = atomIndex;
771       if (getSymViewState() == STATE_SYM_NONE)
772         setSymViewState(STATE_SYM_SHOW);
773       showXtalSymmetry();
774       return true;
775     case STATE_XTALEDIT:
776       if (atomIndex == centerAtomIndex)
777         return true;
778       notImplemented("edit click");
779       return false;
780     }
781     notImplemented("atom click unknown XTAL state");
782     return false;
783   }
784 
785   /**
786    * Called by Viewer.hoverOn to set the special label if desired.
787    *
788    * @param atomIndex
789    * @return special label or null
790    */
getHoverLabel(int atomIndex)791   private String getHoverLabel(int atomIndex) {
792     int state = getMKState();
793     String msg = null;
794     switch (state) {
795     case STATE_XTALVIEW:
796       if (symop == null)
797         symop = Integer.valueOf(1);
798       msg = "view symop " + symop + " for " + vwr.getAtomInfo(atomIndex);
799       break;
800     case STATE_XTALEDIT:
801       msg = "start editing for " + vwr.getAtomInfo(atomIndex);
802       break;
803     case STATE_MOLECULAR:
804       if (isRotateBond) {
805         if (atomIndex == bondAtomIndex1 || atomIndex == bondAtomIndex2) {
806           msg = "rotate branch";
807           branchAtomIndex = atomIndex;
808           bsRotateBranch = null;
809         } else {
810           msg = "rotate bond";
811           bsRotateBranch = null;
812           branchAtomIndex = -1;
813           //          resetBondFields("gethover");
814         }
815       }
816       if (bondIndex < 0) {
817         if (atomHoverLabel.length() <= 2) {
818           msg = atomHoverLabel = "Click to change to " + atomHoverLabel
819               + " or drag to add " + atomHoverLabel;
820         } else {
821           msg = atomHoverLabel;
822           vwr.highlight(BSUtil.newAndSetBit(atomIndex));
823         }
824       } else {
825         if (msg == null) {
826           switch (bsHighlight.cardinality()) {
827           case 0:
828             vwr.highlight(BSUtil.newAndSetBit(atomIndex));
829             //$FALL-THROUGH$
830           case 1:
831             if (!isRotateBond)
832               setActiveMenu(ATOM_MENU);
833             if (atomHoverLabel.indexOf("charge") >= 0) {
834               int ch = vwr.ms.at[atomIndex].getFormalCharge();
835               ch += (atomHoverLabel.indexOf("increase") >= 0 ? 1 :-1);
836               msg = atomHoverLabel + " to " + (ch > 0 ? "+" : "") + ch;
837             } else {
838               msg = atomHoverLabel;
839             }
840             break;
841           case 2:
842             msg = bondHoverLabel;
843             break;
844           }
845         }
846       }
847       break;
848     }
849 
850     return msg;
851   }
852 
setDefaultState(int mode)853   private void setDefaultState(int mode) {
854     if (!hasUnitCell)
855       mode = STATE_MOLECULAR;
856     if (!hasUnitCell || isXtalState() != hasUnitCell) {
857       setMKState(mode);
858       switch (mode) {
859       case STATE_MOLECULAR:
860         break;
861       case STATE_XTALVIEW:
862         if (getSymViewState() == STATE_SYM_NONE)
863           setSymViewState(STATE_SYM_SHOW);
864         break;
865       case STATE_XTALEDIT:
866         break;
867       }
868     }
869   }
870 
871   /////////////////// menu execution //////////////
872 
873   @Override
appGetBooleanProperty(String name)874   protected boolean appGetBooleanProperty(String name) {
875     if (name.startsWith("mk")) {
876       return ((Boolean) getProperty(name.substring(2))).booleanValue();
877     }
878     return vwr.getBooleanProperty(name);
879   }
880 
881   /**
882    * From JmolGenericPopup.appRunSpecialCheckBox when name starts with "mk" or has "??" in it.
883    */
884   @Override
getUnknownCheckBoxScriptToRun(SC item, String name, String what, boolean TF)885   public String getUnknownCheckBoxScriptToRun(SC item, String name, String what,
886                                               boolean TF) {
887     if (name.startsWith("mk")) {
888       processMKPropertyItem(name, TF);
889       return null;
890     }
891     // must be ?? -- atom setting by user input
892     String element = promptUser(GT.$("Element?"), "");
893     if (element == null || Elements.elementNumberFromSymbol(element, true) == 0)
894       return null;
895     menuSetLabel(item, element);
896     item.setActionCommand("assignAtom_" + element + "P!:??");
897     atomHoverLabel = "Click or click+drag for " + element;
898     return "set picking assignAtom_" + element;
899   }
900 
901 
processMKPropertyItem(String name, boolean TF)902   private void processMKPropertyItem(String name, boolean TF) {
903     // set a property
904     // { "xtalOptionsPersistMenu", "mkaddHydrogensCB mkclicktosetelementCB" }
905     name = name.substring(2);
906     int pt = name.indexOf("_");
907     if (pt > 0) {
908       setProperty(name.substring(0, pt), name.substring(pt + 1));
909     } else {
910       setProperty(name, Boolean.valueOf(TF));
911     }
912   }
913 
914   /**
915    * Draw the symmetry element
916    */
showXtalSymmetry()917   private void showXtalSymmetry() {
918     String script = null;
919     switch (getSymViewState()) {
920     case STATE_SYM_NONE:
921       script = "draw * delete";
922       break;
923     case STATE_SYM_SHOW:
924     default:
925       P3 offset = null;
926       if (secondAtomIndex >= 0) {
927         script = "draw ID sym symop "
928             + (centerAtomIndex < 0 ? centerPoint
929                 : " {atomindex=" + centerAtomIndex + "}")
930             + " {atomindex=" + secondAtomIndex + "}";
931       } else {
932         offset = this.viewOffset;
933         if (symop == null)
934           symop = Integer.valueOf(1);
935         int iatom = (centerAtomIndex >= 0 ? centerAtomIndex
936             : centerPoint != null ? -1 : iatom0); // default to first atom
937         script = "draw ID sym symop "
938             + (symop == null ? "1"
939                 : symop instanceof String ? "'" + symop + "'"
940                     : PT.toJSON(null, symop))
941             + (iatom < 0 ? centerPoint : " {atomindex=" + iatom + "}")
942             + (offset == null ? "" : " offset " + offset);
943       }
944       drawData = runScriptBuffered(script);
945       drawScript = script;
946       drawData = (showSymopInfo
947           ? drawData.substring(0, drawData.indexOf("\n") + 1)
948           : "");
949       appRunScript(
950            ";refresh;set echo top right;echo " + drawData.replace('\t', ' ')
951           );
952       break;
953     }
954   }
955 
956   /**
957    * Original ModelKitPopup functionality -- assign an atom.
958    *
959    * @param atomIndex
960    * @param type
961    * @param autoBond
962    * @param addHsAndBond
963    * @param isClick whether this is a click or not
964    */
assignAtom(int atomIndex, String type, boolean autoBond, boolean addHsAndBond, boolean isClick)965   private void assignAtom(int atomIndex, String type, boolean autoBond,
966                           boolean addHsAndBond, boolean isClick) {
967     if (isClick) {
968 
969       if (isVwrRotateBond()) {
970         bondAtomIndex1 = atomIndex;
971         return;
972       }
973 
974       if (processAtomClick(atomIndex) || !clickToSetElement
975           && vwr.ms.getAtom(atomIndex).getElementNumber() != 1)
976         return;
977 
978     }
979     Atom atom = vwr.ms.at[atomIndex];
980     if (atom == null)
981       return;
982     vwr.ms.clearDB(atomIndex);
983     if (type == null)
984       type = "C";
985 
986     // not as simple as just defining an atom.
987     // if we click on an H, and C is being defined,
988     // this sprouts an sp3-carbon at that position.
989 
990     BS bs = new BS();
991     boolean wasH = (atom.getElementNumber() == 1);
992     int atomicNumber = (PT.isUpperCase(type.charAt(0))
993         ? Elements.elementNumberFromSymbol(type, true)
994         : -1);
995 
996     // 1) change the element type or charge
997 
998     boolean isDelete = false;
999     if (atomicNumber > 0) {
1000       boolean doTaint = (atomicNumber > 1 || !addHsAndBond);
1001       vwr.ms.setElement(atom, atomicNumber, doTaint);
1002       vwr.shm.setShapeSizeBs(JC.SHAPE_BALLS, 0, vwr.rd,
1003           BSUtil.newAndSetBit(atomIndex));
1004       vwr.ms.setAtomName(atomIndex, type + atom.getAtomNumber(), doTaint);
1005       if (vwr.getBoolean(T.modelkitmode))
1006         vwr.ms.am[atom.mi].isModelKit = true;
1007       if (!vwr.ms.am[atom.mi].isModelKit || atomicNumber > 1)
1008         vwr.ms.taintAtom(atomIndex, AtomCollection.TAINT_ATOMNAME);
1009     } else if (type.toLowerCase().equals("pl")) {
1010       atom.setFormalCharge(atom.getFormalCharge() + 1);
1011     } else if (type.toLowerCase().equals("mi")) {
1012       atom.setFormalCharge(atom.getFormalCharge() - 1);
1013     } else if (type.equals("X")) {
1014       isDelete = true;
1015     } else if (!type.equals(".") || !addXtalHydrogens) {
1016       return; // uninterpretable
1017     }
1018 
1019     if (!addHsAndBond)
1020       return;
1021 
1022     // type = "." is for connect
1023 
1024     // 2) delete noncovalent bonds and attached hydrogens for that atom.
1025 
1026     vwr.ms.removeUnnecessaryBonds(atom, isDelete);
1027 
1028     // 3) adjust distance from previous atom.
1029 
1030     float dx = 0;
1031     if (atom.getCovalentBondCount() == 1)
1032       if (wasH) {
1033         dx = 1.50f;
1034       } else if (!wasH && atomicNumber == 1) {
1035         dx = 1.0f;
1036       }
1037     if (dx != 0) {
1038       V3 v = V3.newVsub(atom, vwr.ms.at[atom.getBondedAtomIndex(0)]);
1039       float d = v.length();
1040       v.normalize();
1041       v.scale(dx - d);
1042       vwr.ms.setAtomCoordRelative(atomIndex, v.x, v.y, v.z);
1043     }
1044 
1045     BS bsA = BSUtil.newAndSetBit(atomIndex);
1046 
1047     if (isDelete) {
1048       vwr.deleteAtoms(bsA, false);
1049     }
1050     if (atomicNumber != 1 && autoBond) {
1051 
1052       // 4) clear out all atoms within 1.0 angstrom
1053       vwr.ms.validateBspf(false);
1054       bs = vwr.ms.getAtomsWithinRadius(1.0f, bsA, false, null);
1055       bs.andNot(bsA);
1056       if (bs.nextSetBit(0) >= 0)
1057         vwr.deleteAtoms(bs, false);
1058 
1059       // 5) attach nearby non-hydrogen atoms (rings)
1060 
1061       bs = vwr.getModelUndeletedAtomsBitSet(atom.mi);
1062       bs.andNot(vwr.ms.getAtomBitsMDa(T.hydrogen, null, new BS()));
1063       vwr.ms.makeConnections2(0.1f, 1.8f, 1, T.create, bsA, bs, null, false,
1064           false, 0);
1065 
1066       // 6) add hydrogen atoms
1067 
1068     }
1069 
1070     if (addXtalHydrogens)
1071       vwr.addHydrogens(bsA, Viewer.MIN_SILENT);
1072   }
1073 
1074   /**
1075    * Original ModelKit functionality -- assign a bond.
1076    *
1077    * @param bondIndex
1078    * @param type
1079    * @return bit set of atoms to modify
1080    */
assignBond(int bondIndex, char type)1081   private BS assignBond(int bondIndex, char type) {
1082     int bondOrder = type - '0';
1083     Bond bond = vwr.ms.bo[bondIndex];
1084     vwr.ms.clearDB(bond.atom1.i);
1085     switch (type) {
1086     case '0':
1087     case '1':
1088     case '2':
1089     case '3':
1090     case '4':
1091     case '5':
1092       break;
1093     case 'p':
1094     case 'm':
1095       bondOrder = Edge.getBondOrderNumberFromOrder(bond.getCovalentOrder())
1096           .charAt(0) - '0' + (type == 'p' ? 1 : -1);
1097       if (bondOrder > 3)
1098         bondOrder = 1;
1099       else if (bondOrder < 0)
1100         bondOrder = 3;
1101       break;
1102     default:
1103       return null;
1104     }
1105     BS bsAtoms = new BS();
1106     try {
1107       if (bondOrder == 0) {
1108         BS bs = new BS();
1109         bs.set(bond.index);
1110         bsAtoms.set(bond.atom1.i);
1111         bsAtoms.set(bond.atom2.i);
1112         vwr.ms.deleteBonds(bs, false);
1113       } else {
1114         bond.setOrder(bondOrder | Edge.BOND_NEW);
1115         if (bond.atom1.getElementNumber() != 1
1116             && bond.atom2.getElementNumber() != 1) {
1117           vwr.ms.removeUnnecessaryBonds(bond.atom1, false);
1118           vwr.ms.removeUnnecessaryBonds(bond.atom2, false);
1119         }
1120         bsAtoms.set(bond.atom1.i);
1121         bsAtoms.set(bond.atom2.i);
1122       }
1123     } catch (Exception e) {
1124       Logger.error("Exception in seBondOrder: " + e.toString());
1125     }
1126     if (type != '0' && addXtalHydrogens)
1127       vwr.addHydrogens(bsAtoms, Viewer.MIN_SILENT);
1128     return bsAtoms;
1129   }
1130 
isVwrRotateBond()1131   private boolean isVwrRotateBond() {
1132     return (vwr.acm.getBondPickingMode() == ActionManager.PICKING_ROTATE_BOND);
1133   }
1134 
getRotateBondIndex()1135   public int getRotateBondIndex() {
1136     return (getMKState() == STATE_MOLECULAR && isRotateBond ? bondIndex : -1);
1137   }
1138 
1139   /**
1140    * @param where
1141    */
resetBondFields(String where)1142   private void resetBondFields(String where) {
1143     bsRotateBranch = null;
1144     // do not set bondIndex to -1 here
1145     branchAtomIndex = bondAtomIndex1 = bondAtomIndex2 = -1;
1146   }
1147 
1148   /**
1149    * Set the bond for rotation -- called by Sticks.checkObjectHovered via
1150    * Viewer.highlightBond.
1151    *
1152    *
1153    * @param index
1154    * @param isRotate
1155    */
setBondIndex(int index, boolean isRotate)1156   private void setBondIndex(int index, boolean isRotate) {
1157     if (!isRotate && isVwrRotateBond()) {
1158       vwr.setModelKitRotateBondIndex(index);
1159       return;
1160     }
1161 
1162     boolean haveBond = (bondIndex >= 0);
1163     if (!haveBond && index < 0)
1164       return;
1165     if (index < 0) {
1166       resetBondFields("setbondindex<0");
1167       return;
1168     }
1169 
1170     bsRotateBranch = null;
1171     branchAtomIndex = -1;
1172     bondIndex = index;
1173     isRotateBond = isRotate;
1174     bondAtomIndex1 = vwr.ms.bo[index].getAtomIndex1();
1175     bondAtomIndex2 = vwr.ms.bo[index].getAtomIndex2();
1176     setActiveMenu(BOND_MENU);
1177   }
1178 
1179 
1180   /**
1181    * Actually rotate the bond. Called by ActionManager.checkDragWheelAction.
1182    *
1183    * @param deltaX
1184    * @param deltaY
1185    * @param x
1186    * @param y
1187    * @param forceFull
1188    */
actionRotateBond(int deltaX, int deltaY, int x, int y, boolean forceFull)1189   public void actionRotateBond(int deltaX, int deltaY, int x, int y, boolean forceFull) {
1190 
1191     if (bondIndex < 0)
1192       return;
1193     BS bsBranch = bsRotateBranch;
1194     Atom atomFix, atomMove;
1195     ModelSet ms = vwr.ms;
1196     if (forceFull) {
1197       bsBranch = null;
1198       branchAtomIndex = -1;
1199     }
1200     if (bsBranch == null) {
1201       Bond b = ms.bo[bondIndex];
1202       atomMove = (branchAtomIndex == b.atom1.i ? b.atom1 : b.atom2);
1203       atomFix = (atomMove == b.atom1 ? b.atom2 : b.atom1);
1204       vwr.undoMoveActionClear(atomFix.i, AtomCollection.TAINT_COORD, true);
1205 
1206       if (branchAtomIndex >= 0)
1207         bsBranch = vwr.getBranchBitSet(atomMove.i, atomFix.i, true);
1208       if (bsBranch != null)
1209         for (int n = 0, i = atomFix.bonds.length; --i >= 0;) {
1210           if (bsBranch.get(atomFix.getBondedAtomIndex(i)) && ++n == 2) {
1211             bsBranch = null;
1212             break;
1213           }
1214         }
1215       if (bsBranch == null) {
1216         bsBranch = ms.getMoleculeBitSetForAtom(atomFix.i);
1217       }
1218       bsRotateBranch = bsBranch;
1219       bondAtomIndex1 = atomFix.i;
1220       bondAtomIndex2 = atomMove.i;
1221     } else {
1222       atomFix = ms.at[bondAtomIndex1];
1223       atomMove = ms.at[bondAtomIndex2];
1224     }
1225     V3 v1 = V3.new3(atomMove.sX - atomFix.sX, atomMove.sY - atomFix.sY, 0);
1226     V3 v2 = V3.new3(deltaX, deltaY, 0);
1227     v1.cross(v1, v2);
1228     float degrees = (v1.z > 0 ? 1 : -1) * v2.length();
1229 
1230     BS bs = BSUtil.copy(bsBranch);
1231     bs.andNot(vwr.slm.getMotionFixedAtoms());
1232     vwr.rotateAboutPointsInternal(null, atomFix, atomMove, 0, degrees, false, bs,
1233         null, null, null, null);
1234   }
1235 
1236 ////////////// more callback methods //////////////
1237 
1238   @Override
menuFocusCallback(String name, String actionCommand, boolean gained)1239   public void menuFocusCallback(String name, String actionCommand,
1240                                 boolean gained) {
1241     if (gained && !processSymop(name, true)) {
1242         setActiveMenu(name);
1243     }
1244     exitBondRotation();
1245   }
1246 
exitBondRotation()1247   protected void exitBondRotation() {
1248     System.out.println("MKP exitBondRotation");
1249     isRotateBond = false;
1250     vwr.highlight(null);
1251     if (prevBondCheckBox != null)
1252       bondHoverLabel = prevBondCheckBox.getText();
1253     vwr.setPickingMode(null, ActionManager.PICKING_ASSIGN_BOND);
1254   }
1255 
1256   @Override
menuClickCallback(SC source, String script)1257   public void menuClickCallback(SC source, String script) {
1258     doMenuClickCallbackMK(source, script);
1259   }
1260 
doMenuClickCallbackMK(SC source, String script)1261     public void doMenuClickCallbackMK(SC source, String script) {
1262       //action performed
1263       if (processSymop(source.getName(), false))
1264         return;
1265       if (script.equals("clearQPersist")) {
1266         for (SC item : htCheckbox.values()) {
1267           if (item.getActionCommand().indexOf(":??") < 0)
1268             continue;
1269           menuSetLabel(item, "??");
1270           item.setActionCommand("_??P!:");
1271           item.setSelected(false);
1272         }
1273         appRunScript("set picking assignAtom_C");
1274         return;
1275       }
1276       // may come back to getScriptForCallback
1277      doMenuClickCallback(source, script);
1278     }
1279 
1280   /**
1281    * Secondary processing of menu item click
1282    */
1283   @Override
getScriptForCallback(SC source, String id, String script)1284   protected String getScriptForCallback(SC source, String id, String script) {
1285     if (script.startsWith("mk")) {
1286       processXtalClick(id, script);
1287       script = null; // cancels any further processing
1288     }
1289     return script;
1290   }
1291 
processXtalClick(String id, String action)1292   private void processXtalClick(String id, String action) {
1293     if (processSymop(id, false))
1294       return;
1295     action = action.intern();
1296     if (action.startsWith("mkmode_")) {
1297       if (!alertedNoEdit && action == "mkmode_edit") {
1298         alertedNoEdit = true;
1299         vwr.alert("ModelKit xtal edit has not been implemented");
1300         return;
1301       }
1302       processModeClick(action);
1303     } else if (action.startsWith("mksel_")) {
1304       processSelClick(action);
1305     } else if (action.startsWith("mkselop_")) {
1306       processSelOpClick(action);
1307     } else if (action.startsWith("mksymmetry_")) {
1308       processSymClick(action);
1309     } else if (action.startsWith("mkunitcell_")) {
1310       processUCClick(action);
1311     } else {
1312       notImplemented("XTAL click " + action);
1313     }
1314     updateAllXtalMenuOptions();
1315   }
processSelOpClick(String action)1316   private void processSelOpClick(String action) {
1317     secondAtomIndex = -1;
1318     if (action == "mkselop_addoffset") {
1319       String pos = promptUser("Enter i j k for an offset for viewing the operator - leave blank to clear", lastOffset);
1320       if (pos == null)
1321         return;
1322       lastOffset = pos;
1323       if (pos.length() == 0 || pos == "none") {
1324         setProperty("offset", "none");
1325         return;
1326       }
1327       P3 p = pointFromTriad(pos);
1328       if (p == null) {
1329         processSelOpClick(action);
1330       } else {
1331         setProperty("offset", p);
1332       }
1333     } else if (action == "mkselop_atom2") {
1334       notImplemented(action);
1335     }
1336   }
1337 
processSymop(String id, boolean isFocus)1338   private boolean processSymop(String id, boolean isFocus) {
1339     int pt = id.indexOf(".mkop_");
1340     if (pt >= 0) {
1341       Object op = symop;
1342       symop = Integer.valueOf(id.substring(pt + 6));
1343       showSymop(symop);
1344       if (isFocus) // temporary only
1345         symop = op;
1346       return true;
1347     }
1348     return false;
1349   }
1350 
showSymop(Object symop)1351   private void showSymop(Object symop) {
1352     secondAtomIndex = -1;
1353     this.symop = symop;
1354     showXtalSymmetry();
1355   }
1356 
processModeClick(String action)1357   private void processModeClick(String action) {
1358     processMKPropertyItem(action, false);
1359   }
1360 
processSelClick(String action)1361   private void processSelClick(String action) {
1362     if (action == "mksel_atom") {
1363       centerPoint = null;
1364       centerAtomIndex = -1;
1365       secondAtomIndex = -1;
1366       // indicate next click is an atom
1367     } else if (action == "mksel_position") {
1368       String pos = promptUser("Enter three fractional coordinates", lastCenter);
1369       if (pos == null)
1370         return;
1371       lastCenter = pos;
1372       P3 p = pointFromTriad(pos);
1373       if (p == null) {
1374         processSelClick(action);
1375         return;
1376       }
1377       centerAtomIndex = -Integer.MAX_VALUE;
1378       centerPoint = p;
1379       showXtalSymmetry();
1380     }
1381   }
1382 
processSymClick(String action)1383   private void processSymClick(String action) {
1384     if (action == "mksymmetry_none") {
1385       setSymEdit(STATE_SYM_NONE);
1386     } else {
1387       processMKPropertyItem(action, false);
1388     }
1389   }
1390 
processUCClick(String action)1391   private void processUCClick(String action) {
1392     processMKPropertyItem(action, false);
1393     showXtalSymmetry();
1394   }
1395 
1396   /**
1397    * Called from ActionManager for a drag-drop
1398    *
1399    * @param pressed
1400    * @param dragged
1401    * @param countPlusIndices
1402    * @return true if handled here
1403    */
handleDragAtom(MouseState pressed, MouseState dragged, int[] countPlusIndices)1404   public boolean handleDragAtom(MouseState pressed, MouseState dragged,
1405                                 int[] countPlusIndices) {
1406     switch (getMKState()) {
1407     case STATE_MOLECULAR:
1408       return false;
1409     case STATE_XTALEDIT:
1410       if (countPlusIndices[0] > 2)
1411         return true;
1412       notImplemented("drag atom for XTAL edit");
1413       break;
1414     case STATE_XTALVIEW:
1415       if (getSymViewState() == STATE_SYM_NONE)
1416         setSymViewState(STATE_SYM_SHOW);
1417       switch (countPlusIndices[0]) {
1418       case 1:
1419         centerAtomIndex = countPlusIndices[1];
1420         secondAtomIndex = -1;
1421         break;
1422       case 2:
1423         centerAtomIndex = countPlusIndices[1];
1424         secondAtomIndex = countPlusIndices[2];
1425         break;
1426       }
1427       showXtalSymmetry();
1428       return true;
1429     }
1430     return true;
1431   }
1432 
pointFromTriad(String pos)1433   private static P3 pointFromTriad(String pos) {
1434     float[] a = PT.parseFloatArray(PT.replaceAllCharacters(pos,  "{,}", " "));
1435     return (a.length == 3 && !Float.isNaN(a[2]) ? P3.new3(a[0], a[1], a[2]) : null);
1436   }
1437 
notImplemented(String action)1438   private static void notImplemented(String action) {
1439     System.err.println("ModelKitPopup.notImplemented(" + action + ")");
1440   }
1441 
promptUser(String msg, String def)1442   private String promptUser(String msg, String def) {
1443     return vwr.prompt(msg, def, null, false);
1444   }
1445 
runScriptBuffered(String script)1446   private String runScriptBuffered(String script) {
1447     SB sb = new SB();
1448     try {
1449      // System.out.println("MKP\n" + script);
1450       ((ScriptEval) vwr.eval).runBufferedSafely(script, sb);
1451     } catch (Exception e) {
1452       e.printStackTrace();
1453     }
1454     return sb.toString();
1455   }
1456 
1457   /**
1458    * C
1459    *
1460    * @param pressed
1461    * @param dragged
1462    * @param mp
1463    * @param dragAtomIndex
1464    * @return true if we should do a refresh now
1465    */
handleAssignNew(MouseState pressed, MouseState dragged, MeasurementPending mp, int dragAtomIndex)1466   public boolean handleAssignNew(MouseState pressed, MouseState dragged,
1467                                  MeasurementPending mp, int dragAtomIndex) {
1468 
1469     // H C + -, etc.
1470     // also check valence and add/remove H atoms as necessary?
1471     boolean inRange = pressed.inRange(ActionManager.XY_RANGE, dragged.x,
1472         dragged.y);
1473 
1474     if (inRange) {
1475       dragged.x = pressed.x;
1476       dragged.y = pressed.y;
1477     }
1478 
1479     if (handleDragAtom(pressed, dragged, mp.countPlusIndices))
1480       return true;
1481     boolean isCharge = isPickAtomAssignCharge;
1482     String atomType = pickAtomAssignType;
1483     if (mp.count == 2) {
1484       vwr.undoMoveActionClear(-1, T.save, true);
1485       if (((Atom) mp.getAtom(1)).isBonded((Atom) mp.getAtom(2))) {
1486         appRunScript("modelkit assign bond " + mp.getMeasurementScript(" ", false) + "'p'");
1487       } else {
1488         appRunScript("modelkit connect " + mp.getMeasurementScript(" ", false));
1489       }
1490     } else {
1491       if (atomType.equals("Xx")) {
1492         atomType = lastElementType;
1493       }
1494       if (inRange) {
1495         String s = "modelkit assign atom ({" + dragAtomIndex + "}) \"" + atomType + "\" true";
1496         if (isCharge) {
1497           s += ";{atomindex=" + dragAtomIndex + "}.label='%C'; ";
1498           vwr.undoMoveActionClear(dragAtomIndex,
1499               AtomCollection.TAINT_FORMALCHARGE, true);
1500         } else {
1501           vwr.undoMoveActionClear(-1, T.save, true);
1502         }
1503         appRunScript(s);
1504       } else if (!isCharge) {
1505         vwr.undoMoveActionClear(-1, T.save, true);
1506         Atom a = vwr.ms.at[dragAtomIndex];
1507         if (a.getElementNumber() == 1) {
1508           assignAtomClick(dragAtomIndex, "X", null);
1509         } else {
1510           int x = dragged.x;
1511           int y = dragged.y;
1512 
1513           if (vwr.antialiased) {
1514             x <<= 1;
1515             y <<= 1;
1516           }
1517 
1518           P3 ptNew = P3.new3(x, y, a.sZ);
1519           vwr.tm.unTransformPoint(ptNew, ptNew);
1520           assignAtomClick(dragAtomIndex, atomType, ptNew);
1521         }
1522       }
1523     }
1524     return true;
1525   }
1526 
cmdAssignAtom(int atomIndex, P3 pt, String type, String cmd, boolean isClick)1527   public void cmdAssignAtom(int atomIndex, P3 pt, String type, String cmd, boolean isClick) {
1528     if (isClick && type.equals("X"))
1529       vwr.setModelKitRotateBondIndex(-1);
1530     int ac = vwr.ms.ac;
1531     Atom atom = (atomIndex < 0 ? null : vwr.ms.at[atomIndex]);
1532     if (pt == null) {
1533       if (atomIndex < 0 || atom == null)
1534         return;
1535       int mi = atom.mi;
1536       vwr.sm.modifySend(atomIndex, mi, 1, cmd);
1537       // After this next command, vwr.modelSet will be a different instance
1538       assignAtom(atomIndex, type, autoBond, true, true);
1539       if (!PT.isOneOf(type, ";Mi;Pl;X;"))
1540         vwr.ms.setAtomNamesAndNumbers(0, -ac, null);
1541       vwr.sm.modifySend(atomIndex, mi, -1, "OK");
1542       vwr.refresh(Viewer.REFRESH_SYNC_MASK, "assignAtom");
1543       return;
1544     }
1545     BS bs = (atomIndex < 0 ? new BS() : BSUtil.newAndSetBit(atomIndex));
1546     P3[] pts = new P3[] { pt };
1547     Lst<Atom> vConnections = new Lst<Atom>();
1548     int modelIndex = -1;
1549     if (atom != null) {
1550       vConnections.addLast(atom);
1551       modelIndex = atom.mi;
1552       vwr.sm.modifySend(atomIndex, modelIndex, 3, cmd);
1553     }
1554     try {
1555       int pickingMode = vwr.acm.getAtomPickingMode();
1556       boolean wasHidden = hidden;
1557       boolean isMK = vwr.getBoolean(T.modelkitmode);
1558       if (!isMK) {
1559         allowPopup = false;
1560         vwr.setBooleanProperty("modelkitmode", true);
1561         hidden = true;
1562       }
1563       bs = vwr.addHydrogensInline(bs, vConnections, pts);
1564       if (!isMK) {
1565         vwr.setBooleanProperty("modelkitmode", false);
1566         hidden = wasHidden;
1567         allowPopup = true;
1568         vwr.acm.setPickingMode(pickingMode);
1569         menuHidePopup(popupMenu);
1570       }
1571       int atomIndex2 = bs.nextSetBit(0);
1572       assignAtom(atomIndex2, type, false, atomIndex >= 0, true);
1573       if (atomIndex >= 0)
1574         assignAtom(atomIndex, ".", false, true, isClick);
1575       atomIndex = atomIndex2;
1576     } catch (Exception ex) {
1577       //
1578     }
1579     vwr.ms.setAtomNamesAndNumbers(0, -ac, null);
1580     vwr.sm.modifySend(atomIndex, modelIndex, -3, "OK");
1581   }
1582 
cmdAssignBond(int bondIndex, char type, String cmd)1583   public void cmdAssignBond(int bondIndex, char type, String cmd) {
1584     int modelIndex = -1;
1585     try {
1586       if (type == '-')
1587         type = pickBondAssignType;
1588       modelIndex = vwr.ms.bo[bondIndex].atom1.mi;
1589       int ac = vwr.ms.ac;
1590       vwr.sm.modifySend(bondIndex, modelIndex, 2,
1591           cmd);
1592       BS bsAtoms = assignBond(bondIndex, type);
1593       vwr.ms.setAtomNamesAndNumbers(0, -ac, null);
1594       if (bsAtoms == null || type == '0')
1595         vwr.refresh(Viewer.REFRESH_SYNC_MASK, "setBondOrder");
1596       vwr.sm.modifySend(bondIndex, modelIndex, -2, "" + type);
1597     } catch (Exception ex) {
1598       Logger.error("assignBond failed");
1599       vwr.sm.modifySend(bondIndex, modelIndex, -2, "ERROR " + ex);
1600     }
1601   }
1602 
cmdAssignConnect(int index, int index2, char type, String cmd)1603   public void cmdAssignConnect(int index, int index2, char type, String cmd) {
1604     float[][] connections = AU.newFloat2(1);
1605     connections[0] = new float[] { index, index2 };
1606     int modelIndex = vwr.ms.at[index].mi;
1607     vwr.sm.modifySend(index, modelIndex, 2, cmd);
1608     vwr.ms.connect(connections);
1609     int ac = vwr.ms.ac;
1610     // note that vwr.ms changes during the assignAtom command
1611     assignAtom(index, ".", true, true, false);
1612     assignAtom(index2, ".", true, true, false);
1613     vwr.ms.setAtomNamesAndNumbers(0, -ac, null);
1614     vwr.sm.modifySend(index, modelIndex, -2, "OK");
1615     if (type != '1') {
1616       BS bs = BSUtil.newAndSetBit(index);
1617       bs.set(index2);
1618       bs = vwr.getBondsForSelectedAtoms(bs);
1619       int bondIndex = bs.nextSetBit(0);
1620       cmdAssignBond(bondIndex, type, cmd);
1621     }
1622     vwr.refresh(Viewer.REFRESH_SYNC_MASK, "assignConnect");
1623   }
1624 
assignAtomClick(int atomIndex, String element, P3 ptNew)1625   public void assignAtomClick(int atomIndex, String element, P3 ptNew) {
1626 //    if (vwr.ms.isAtomInLastModel(atomIndex)) {
1627       vwr.script("modelkit assign atom ({" + atomIndex + "}) \"" + element + "\" "
1628           + (ptNew == null ? "" : Escape.eP(ptNew)) + " true");
1629 //    }
1630   }
1631 
1632   @Override
appRunSpecialCheckBox(SC item, String basename, String script, boolean TF)1633   protected boolean appRunSpecialCheckBox(SC item, String basename, String script,
1634                                          boolean TF) {
1635     if (basename.indexOf("assignAtom_Xx") == 0) {
1636       pickAtomAssignType = lastElementType;
1637     }
1638     return super.appRunSpecialCheckBox(item, basename, script, TF);
1639   }
1640 
getDefaultModel()1641   public String getDefaultModel() {
1642     return (addXtalHydrogens ? JC.MODELKIT_ZAP_STRING : "1\n\nC 0 0 0\n");
1643   }
1644 }
1645