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