1 package org.jmol.popup;
2 
3 import java.util.Hashtable;
4 import java.util.Map;
5 import java.util.StringTokenizer;
6 
7 import org.jmol.api.GenericMenuInterface;
8 import org.jmol.api.SC;
9 import org.jmol.util.Logger;
10 
11 import javajs.util.Lst;
12 import javajs.util.PT;
13 import javajs.util.SB;
14 
15 /**
16  *
17  * The overall parent of all popup classes in Jmol and JSmol.
18  * Contains methods and fields common to the "SwingComponent" SC class,
19  * which allows for both JavaScript (org.jmol.awtjs.swing) and Java (java.awt) components.
20  *
21  * This solution predates Jmol-SwingJS by about six years (2012 vs. 2018)
22  *
23  * <pre>
24  * abstract GenericPopop
25  * -- abstract JmolGenericPopup
26  *   -- abstract JmolPopup
27  *      -- AwtJmolPopup
28  *      -- JSJmolPopup
29  *   -- abstract ModelKitPopup
30  *      -- AwtModelKitPopup
31  *      -- JSModelKitPopup
32  * -- abstract JSVGenericPopup
33  *   -- AwtPopup
34  *   -- JsPopup
35  * </pre>
36  *
37  * @author Bob Hanson
38  *
39  */
40 public abstract class GenericPopup implements GenericMenuInterface {
41 
getImageIcon(String fileName)42   abstract protected Object getImageIcon(String fileName);
43 
menuShowPopup(SC popup, int x, int y)44   abstract protected void menuShowPopup(SC popup, int x, int y);
45 
getUnknownCheckBoxScriptToRun(SC item, String name, String what, boolean TF)46   abstract protected String getUnknownCheckBoxScriptToRun(SC item, String name,
47                                                   String what, boolean TF);
48 
49   /**
50    * Opportunity to do something special with an item.
51    *
52    * @param item
53    * @param newMenu
54    */
appCheckItem(String item, SC newMenu)55   protected void appCheckItem(String item, SC newMenu) {
56   }
57 
58 
59   /**
60    * Opportunity to do something special with a given submenu is created
61    * @param item
62    * @param subMenu
63    * @param word
64    */
appCheckSpecialMenu(String item, SC subMenu, String word)65   public void appCheckSpecialMenu(String item, SC subMenu, String word) {
66     // when adding a menu item
67   }
68 
appFixLabel(String label)69   abstract protected String appFixLabel(String label);
70 
getScriptForCallback(SC source, String name, String script)71   abstract protected String getScriptForCallback(SC source, String name, String script);
72 
appGetBooleanProperty(String name)73   abstract protected boolean appGetBooleanProperty(String name);
74 
appRunSpecialCheckBox(SC item, String basename, String what, boolean TF)75   abstract protected boolean appRunSpecialCheckBox(SC item, String basename,
76                                                   String what, boolean TF);
77 
appRestorePopupMenu()78   abstract protected void appRestorePopupMenu();
79 
appRunScript(String script)80   abstract protected void appRunScript(String script);
81 
appUpdateSpecialCheckBoxValue(SC source, String actionCommand, boolean selected)82   abstract protected void appUpdateSpecialCheckBoxValue(SC source,
83                                                         String actionCommand,
84                                                         boolean selected);
85 
appUpdateForShow()86   abstract protected void appUpdateForShow();
87 
88   protected PopupHelper helper;
89 
90   protected String strMenuStructure;
91 
92   protected boolean allowSignedFeatures;
93   protected boolean isJS, isApplet, isSigned, isWebGL;
94   public int thisx, thisy;
95 
96   protected boolean isTainted = true;
97 
98   protected String menuName;
99   protected SC popupMenu;
100   protected SC thisPopup;
101   protected Map<String, SC> htCheckbox = new Hashtable<String, SC>();
102   protected Object buttonGroup;
103   protected String currentMenuItemId;
104   protected Map<String, SC> htMenus = new Hashtable<String, SC>();
105   private Lst<SC> SignedOnly = new Lst<SC>();
106 
107   protected boolean updatingForShow;
108 
initSwing(String title, PopupResource bundle, Object applet, boolean isJS, boolean isSigned, boolean isWebGL)109   protected void initSwing(String title, PopupResource bundle, Object applet,
110                            boolean isJS, boolean isSigned, boolean isWebGL) {
111     this.isJS = isJS;
112     this.isApplet = (applet != null);
113     this.isSigned = isSigned;
114     this.isWebGL = isWebGL;
115     this.allowSignedFeatures = (!isApplet || isSigned);
116     menuName = title;
117     popupMenu = helper.menuCreatePopup(title, applet);
118     thisPopup = popupMenu;
119     htMenus.put(title, popupMenu);
120     addMenuItems("", title, popupMenu, bundle);
121 //    try {
122 //      jpiUpdateComputedMenus();
123 //    } catch (NullPointerException e) {
124 //      // ignore -- the frame just wasn't ready yet;
125 //      // updateComputedMenus() will be called again when the frame is ready;
126 //    }
127     }
128 
addMenuItems(String parentId, String key, SC menu, PopupResource popupResourceBundle)129   public void addMenuItems(String parentId, String key, SC menu,
130                               PopupResource popupResourceBundle) {
131     String id = parentId + "." + key;
132     String value = popupResourceBundle.getStructure(key);
133     if (Logger.debugging)
134       Logger.debug(id + " --- " + value);
135     if (value == null) {
136       menuCreateItem(menu, "#" + key, "", "");
137       return;
138     }
139     // process predefined @terms
140     StringTokenizer st = new StringTokenizer(value);
141     String item;
142     while (value.indexOf("@") >= 0) {
143       String s = "";
144       while (st.hasMoreTokens())
145         s += " " + ((item = st.nextToken()).startsWith("@")
146             ? popupResourceBundle.getStructure(item)
147             : item);
148       value = s.substring(1);
149       st = new StringTokenizer(value);
150     }
151     while (st.hasMoreTokens()) {
152       item = st.nextToken();
153       if (!checkKey(item))
154         continue;
155       if ("-".equals(item)) {
156         menuAddSeparator(menu);
157         helper.menuAddButtonGroup(null);
158         continue;
159       }
160       String label = popupResourceBundle.getWord(item);
161       SC newItem = null;
162       String script = "";
163       boolean isCB = false;
164       label = appFixLabel(label == null ? item : label);
165       if (label.equals("null")) {
166         // user has taken this menu item out
167         continue;
168       }
169       if (item.indexOf("Menu") >= 0) {
170         if (item.indexOf("more") < 0)
171           helper.menuAddButtonGroup(null);
172         SC subMenu = menuNewSubMenu(label, id + "." + item);
173         menuAddSubMenu(menu, subMenu);
174         addMenu(id, item, subMenu, label, popupResourceBundle);
175         newItem = subMenu;
176       } else if (item.endsWith("Checkbox")
177           || (isCB = (item.endsWith("CB") || item.endsWith("RD")))) {
178         // could be "PRD" -- set picking radio
179         script = popupResourceBundle.getStructure(item);
180         String basename = item.substring(0, item.length() - (!isCB ? 8 : 2));
181         boolean isRadio = (isCB && item.endsWith("RD"));
182         if (script == null || script.length() == 0 && !isRadio)
183           script = "set " + basename + " T/F";
184         newItem = menuCreateCheckboxItem(menu, label, basename + ":" + script,
185             id + "." + item, false, isRadio);
186         rememberCheckbox(basename, newItem);
187         if (isRadio)
188           helper.menuAddButtonGroup(newItem);
189       } else {
190         script = popupResourceBundle.getStructure(item);
191         if (script == null)
192           script = item;
193         newItem = menuCreateItem(menu, label, script, id + "." + item);
194       }
195       // menus or menu items:
196       htMenus.put(item, newItem);
197       // signed items are listed, but not enabled
198       if (item.startsWith("SIGNED")) {
199         SignedOnly.addLast(newItem);
200         if (!allowSignedFeatures)
201           menuEnable(newItem, false);
202       }
203       appCheckItem(item, newItem);
204     }
205   }
206 
addMenu(String id, String item, SC subMenu, String label, PopupResource popupResourceBundle)207   protected void addMenu(String id, String item, SC subMenu, String label,
208                          PopupResource popupResourceBundle) {
209       if (item.indexOf("Computed") < 0)
210         addMenuItems(id, item, subMenu, popupResourceBundle);
211       appCheckSpecialMenu(item, subMenu, label);
212     }
213 
updateSignedAppletItems()214   protected void updateSignedAppletItems() {
215     for (int i = SignedOnly.size(); --i >= 0;)
216       menuEnable(SignedOnly.get(i), allowSignedFeatures);
217   }
218 
219   /**
220    * @param key
221    * @return true unless a JAVA-only key in JavaScript
222    */
checkKey(String key)223   private boolean checkKey(String key) {
224     return (key.indexOf(isApplet ? "JAVA" : "APPLET") < 0
225         && (!isWebGL || key.indexOf("NOGL") < 0));
226   }
227 
rememberCheckbox(String key, SC checkboxMenuItem)228   private void rememberCheckbox(String key, SC checkboxMenuItem) {
229     htCheckbox.put(key + "::" + htCheckbox.size(), checkboxMenuItem);
230   }
231 
updateButton(SC b, String entry, String script)232   protected void updateButton(SC b, String entry, String script) {
233     String[] ret = new String[] { entry };
234     Object icon = getEntryIcon(ret);
235     entry = ret[0];
236     b.init(entry, icon, script, thisPopup);
237     isTainted = true;
238   }
239 
getEntryIcon(String[] ret)240   protected Object getEntryIcon(String[] ret) {
241     String entry = ret[0];
242     if (!entry.startsWith("<"))
243       return null;
244     int pt = entry.indexOf(">");
245     ret[0] = entry.substring(pt + 1);
246     String fileName = entry.substring(1, pt);
247     return getImageIcon(fileName);
248   }
249 
addMenuItem(SC menuItem, String entry)250   protected SC addMenuItem(SC menuItem, String entry) {
251     return menuCreateItem(menuItem, entry, "", null);
252   }
253 
menuSetLabel(SC m, String entry)254   protected void menuSetLabel(SC m, String entry) {
255     if (m == null)
256       return;
257     m.setText(entry);
258     isTainted = true;
259   }
260 
261   /////// run time event-driven methods
262 
menuFocusCallback(String name, String actionCommand, boolean gained)263   abstract public void menuFocusCallback(String name, String actionCommand, boolean gained);
264 
menuClickCallback(SC source, String script)265   public void menuClickCallback(SC source, String script) {
266     doMenuClickCallback(source, script);
267   }
268 
doMenuClickCallback(SC source, String script)269   protected void doMenuClickCallback(SC source, String script) {
270     appRestorePopupMenu();
271     if (script == null || script.length() == 0)
272       return;
273     if (script.equals("MAIN")) {
274       show(thisx, thisy, true);
275       return;
276     }
277     String id = menuGetId(source);
278     if (id != null) {
279       script = getScriptForCallback(source, id, script);
280       currentMenuItemId = id;
281     }
282     if (script != null)
283       appRunScript(script);
284   }
285 
menuCheckBoxCallback(SC source)286   public void menuCheckBoxCallback(SC source) {
287     doMenuCheckBoxCallback(source);
288   }
289 
doMenuCheckBoxCallback(SC source)290   protected void doMenuCheckBoxCallback(SC source) {
291     appRestorePopupMenu();
292     boolean isSelected = source.isSelected();
293     String what = source.getActionCommand();
294     runCheckBoxScript(source, what, isSelected);
295     appUpdateSpecialCheckBoxValue(source, what, isSelected);
296     isTainted = true;
297     String id = menuGetId(source);
298     if (id != null) {
299       currentMenuItemId = id;
300     }
301   }
302 
runCheckBoxScript(SC item, String what, boolean TF)303   private void runCheckBoxScript(SC item, String what, boolean TF) {
304     if (!item.isEnabled())
305       return;
306     if (what.indexOf("##") < 0) {
307       int pt = what.indexOf(":");
308       if (pt < 0) {
309         Logger.error("check box " + item + " IS " + what);
310         return;
311       }
312       // name:trueAction|falseAction
313       String basename = what.substring(0, pt);
314       if (appRunSpecialCheckBox(item, basename, what, TF))
315         return;
316       what = what.substring(pt + 1);
317       if ((pt = what.indexOf("|")) >= 0)
318         what = (TF ? what.substring(0, pt) : what.substring(pt + 1)).trim();
319       what = PT.rep(what, "T/F", (TF ? " TRUE" : " FALSE"));
320     }
321     appRunScript(what);
322   }
323 
menuCreateItem(SC menu, String entry, String script, String id)324   protected SC menuCreateItem(SC menu, String entry, String script, String id) {
325     SC item = helper.getMenuItem(entry);
326     item.addActionListener(helper);
327     return newMenuItem(item, menu, entry, script, id);
328   }
329 
menuCreateCheckboxItem(SC menu, String entry, String basename, String id, boolean state, boolean isRadio)330   protected SC menuCreateCheckboxItem(SC menu, String entry, String basename,
331                                       String id, boolean state,
332                                       boolean isRadio) {
333     SC jmi = (isRadio ? helper.getRadio(entry) : helper.getCheckBox(entry));
334     jmi.setSelected(state);
335     jmi.addItemListener(helper);
336     return newMenuItem(jmi, menu, entry, basename, id);
337   }
338 
menuAddSeparator(SC menu)339   protected void menuAddSeparator(SC menu) {
340     menu.add(helper.getMenuItem(null));
341     isTainted = true;
342   }
343 
menuNewSubMenu(String entry, String id)344   protected SC menuNewSubMenu(String entry, String id) {
345     SC jm = helper.getMenu(entry);
346     jm.addMouseListener(helper);
347     updateButton(jm, entry, null);
348     jm.setName(id);
349     jm.setAutoscrolls(true);
350     return jm;
351   }
352 
menuRemoveAll(SC menu, int indexFrom)353   protected void menuRemoveAll(SC menu, int indexFrom) {
354     if (indexFrom <= 0)
355       menu.removeAll();
356     else
357       for (int i = menu.getComponentCount(); --i >= indexFrom;)
358         menu.remove(i);
359     isTainted = true;
360   }
361 
newMenuItem(SC item, SC menu, String text, String script, String id)362   private SC newMenuItem(SC item, SC menu, String text, String script,
363                          String id) {
364     updateButton(item, text, script);
365     item.addMouseListener(helper);
366     item.setName(id == null ? menu.getName() + "." : id);
367     menuAddItem(menu, item);
368     return item;
369   }
370 
setText(String item, String text)371   protected SC setText(String item, String text) {
372     SC m = htMenus.get(item);
373     if (m != null)
374       m.setText(text);
375     return m;
376   }
377 
menuAddItem(SC menu, SC item)378   private void menuAddItem(SC menu, SC item) {
379     menu.add(item);
380     isTainted = true;
381   }
382 
menuAddSubMenu(SC menu, SC subMenu)383   protected void menuAddSubMenu(SC menu, SC subMenu) {
384     subMenu.addMouseListener(helper);
385     menuAddItem(menu, subMenu);
386   }
387 
menuEnable(SC component, boolean enable)388   protected void menuEnable(SC component, boolean enable) {
389     if (component == null || component.isEnabled() == enable)
390       return;
391     component.setEnabled(enable);
392   }
393 
menuGetId(SC menu)394   protected String menuGetId(SC menu) {
395     return menu.getName();
396   }
397 
menuSetAutoscrolls(SC menu)398   protected void menuSetAutoscrolls(SC menu) {
399     menu.setAutoscrolls(true);
400     isTainted = true;
401   }
402 
menuGetListPosition(SC item)403   protected int menuGetListPosition(SC item) {
404     SC p = (SC) item.getParent();
405     int i;
406     for (i = p.getComponentCount(); --i >= 0;)
407       if (helper.getSwingComponent(p.getComponent(i)) == item)
408         break;
409     return i;
410   }
411 
412   /**
413    * @param x
414    * @param y
415    * @param doPopup
416    */
show(int x, int y, boolean doPopup)417   protected void show(int x, int y, boolean doPopup) {
418     appUpdateForShow();
419     updateCheckBoxesForShow();
420     if (doPopup)
421       menuShowPopup(popupMenu, thisx, thisy);
422   }
423 
updateCheckBoxesForShow()424   private void updateCheckBoxesForShow() {
425     for (Map.Entry<String, SC> entry : htCheckbox.entrySet()) {
426       String key = entry.getKey();
427       SC item = entry.getValue();
428       String basename = key.substring(0, key.indexOf(":"));
429       boolean b = appGetBooleanProperty(basename);
430       updatingForShow = true;
431       if (item.isSelected() != b) {
432         item.setSelected(b);
433         isTainted = true;
434       }
435       updatingForShow = false;
436     }
437   }
438 
439   @Override
jpiGetMenuAsString(String title)440   public String jpiGetMenuAsString(String title) {
441     appUpdateForShow();
442     int pt = title.indexOf("|");
443     if (pt >= 0) {
444       String type = title.substring(pt);
445       title = title.substring(0, pt);
446       if (type.indexOf("current") >= 0) {
447         SB sb = new SB();
448         SC menu = htMenus.get(menuName);
449         menuGetAsText(sb, 0, menu, "PopupMenu");
450         return sb.toString();
451       }
452     }
453     return appGetMenuAsString(title);
454   }
455 
456   /**
457    * @param title
458    * @return null
459    */
appGetMenuAsString(String title)460   protected String appGetMenuAsString(String title) {
461     // main Jmol menu and JSpecView menu only
462     return null;
463   }
464 
menuGetAsText(SB sb, int level, SC menu, String menuName)465   private void menuGetAsText(SB sb, int level, SC menu, String menuName) {
466     String name = menuName;
467     Object[] subMenus = menu.getComponents();
468     String flags = null;
469     String script = null;
470     String text = null;
471     char key = 'S';
472     for (int i = 0; i < subMenus.length; i++) {
473       SC source = helper.getSwingComponent(subMenus[i]);
474       int type = helper.getItemType(source);
475       switch (type) {
476       case 4:
477         key = 'M';
478         name = source.getName();
479         flags = "enabled:" + source.isEnabled();
480         text = source.getText();
481         script = null;
482         break;
483       case 0:
484         key = 'S';
485         flags = script = text = null;
486         break;
487       default:
488         key = 'I';
489         flags = "enabled:" + source.isEnabled();
490         if (type == 2 || type == 3)
491           flags += ";checked:" + source.isSelected();
492         script = getScriptForCallback(source, source.getName(), source.getActionCommand());
493         name = source.getName();
494         text = source.getText();
495         break;
496       }
497       addItemText(sb, key, level, name, text, script, flags);
498       if (type == 2)
499         menuGetAsText(sb, level + 1, helper.getSwingComponent(source.getPopupMenu()),
500             name);
501     }
502   }
503 
addItemText(SB sb, char type, int level, String name, String label, String script, String flags)504   private static void addItemText(SB sb, char type, int level, String name,
505                                   String label, String script, String flags) {
506     sb.appendC(type).appendI(level).appendC('\t').append(name);
507     if (label == null) {
508       sb.append(".\n");
509       return;
510     }
511     sb.append("\t").append(label).append("\t")
512         .append(script == null || script.length() == 0 ? "-" : script)
513         .append("\t").append(flags).append("\n");
514   }
515 
convertToMegabytes(long num)516   static protected int convertToMegabytes(long num) {
517     if (num <= Long.MAX_VALUE - 512 * 1024)
518       num += 512 * 1024;
519     return (int) (num / (1024 * 1024));
520   }
521 
522 }
523