001/* 002 * This file is part of McIDAS-V 003 * 004 * Copyright 2007-2016 005 * Space Science and Engineering Center (SSEC) 006 * University of Wisconsin - Madison 007 * 1225 W. Dayton Street, Madison, WI 53706, USA 008 * https://www.ssec.wisc.edu/mcidas 009 * 010 * All Rights Reserved 011 * 012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and 013 * some McIDAS-V source code is based on IDV and VisAD source code. 014 * 015 * McIDAS-V is free software; you can redistribute it and/or modify 016 * it under the terms of the GNU Lesser Public License as published by 017 * the Free Software Foundation; either version 3 of the License, or 018 * (at your option) any later version. 019 * 020 * McIDAS-V is distributed in the hope that it will be useful, 021 * but WITHOUT ANY WARRANTY; without even the implied warranty of 022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 023 * GNU Lesser Public License for more details. 024 * 025 * You should have received a copy of the GNU Lesser Public License 026 * along with this program. If not, see http://www.gnu.org/licenses. 027 */ 028 029package edu.wisc.ssec.mcidasv.ui; 030 031import java.awt.Color; 032import java.awt.Component; 033import java.awt.Graphics; 034import java.awt.event.ActionEvent; 035import java.awt.event.ActionListener; 036import java.util.ArrayList; 037import java.util.Collections; 038import java.util.Comparator; 039import java.util.List; 040import java.util.Map; 041import java.util.Vector; 042 043import javax.swing.DefaultListCellRenderer; 044import javax.swing.Icon; 045import javax.swing.JButton; 046import javax.swing.JCheckBox; 047import javax.swing.JComboBox; 048import javax.swing.JComponent; 049import javax.swing.JLabel; 050import javax.swing.JList; 051import javax.swing.JMenu; 052import javax.swing.JPanel; 053import javax.swing.JTextField; 054import javax.swing.ListCellRenderer; 055 056import org.w3c.dom.Document; 057import org.w3c.dom.Element; 058 059import edu.wisc.ssec.mcidasv.ui.UIManager.ActionAttribute; 060import edu.wisc.ssec.mcidasv.ui.UIManager.IdvActions; 061 062import ucar.unidata.idv.IdvResourceManager; 063import ucar.unidata.idv.PluginManager; 064import ucar.unidata.ui.TwoListPanel; 065import ucar.unidata.ui.XmlUi; 066import ucar.unidata.util.GuiUtils; 067import ucar.unidata.util.LogUtil; 068import ucar.unidata.util.TwoFacedObject; 069import ucar.unidata.xml.XmlResourceCollection; 070import ucar.unidata.xml.XmlUtil; 071 072public class McvToolbarEditor implements ActionListener { 073 074 /** Size of the icons to be shown in the {@link TwoListPanel}. */ 075 protected static final int ICON_SIZE = 16; 076 077 private static final String MENU_PLUGINEXPORT = "Export to Menu Plugin"; 078 079 private static final String MSG_ENTER_NAME = "Please enter a menu name"; 080 081 private static final String MSG_SELECT_ENTRIES = 082 "Please select entries in the Toolbar list"; 083 084 /** Add a "space" entry */ 085 private static final String CMD_ADDSPACE = "Add Space"; 086 087 /** Action command for reloading the toolbar list with original items */ 088 private static final String CMD_RELOAD = "Reload Original"; 089 090 /** action command */ 091 private static final String CMD_EXPORTPLUGIN = "Export Selected to Plugin"; 092 093 /** action command */ 094 private static final String CMD_EXPORTMENUPLUGIN = 095 "Export Selected to Menu Plugin"; 096 097 /** */ 098 private static final String TT_EXPORT_SELECT = 099 "Export the selected items to the plugin"; 100 101 private static final String TT_EXPORT_SELECTMENU = 102 "Export the selected items as a menu to the plugin"; 103 104 private static final String TT_OVERWRITE = 105 "Select this if you want to replace the selected menu with the new" + 106 "menu."; 107 108 /** ID that represents a "space" in the toolbar. */ 109 private static final String SPACE = "-space-"; 110 111 /** Provides simple IDs for the space entries. */ 112 private int spaceCount = 0; 113 114 /** Used to notify the application that a toolbar update should occur. */ 115 private UIManager uiManager; 116 117 /** All of the toolbar editor's GUI components. */ 118 private JComponent contents; 119 120 /** The GUI component that stores both available and selected actions. */ 121 private TwoListPanel twoListPanel; 122 123 /** The toolbar XML resources. */ 124 XmlResourceCollection resources; 125 126 /** Used to export toolbars to plugin. */ 127 private JTextField menuNameFld; 128 129 /** Used to export toolbars to plugin. */ 130 private JComboBox menuIdBox; 131 132 /** Used to export toolbars to plugin. */ 133 private JCheckBox menuOverwriteCbx; 134 135 /** 136 * Builds a toolbar editor and associates it with the {@link UIManager}. 137 * 138 * @param mngr The application's UI Manager. 139 */ 140 public McvToolbarEditor(final UIManager mngr) { 141 uiManager = mngr; 142 resources = mngr.getIdv().getResourceManager().getXmlResources( 143 IdvResourceManager.RSC_TOOLBAR); 144 init(); 145 } 146 147 /** 148 * Returns the icon associated with {@code actionId}. 149 */ 150 protected Icon getActionIcon(final String actionId) { 151 return uiManager.getActionIcon(actionId, UIManager.ToolbarStyle.SMALL); 152 } 153 154 /** 155 * Determines if a given toolbar entry (in the form of a 156 * {@link ucar.unidata.util.TwoFacedObject}) represents a space. 157 * 158 * @param tfo The entry to test. 159 * 160 * @return Whether or not the entry represents a space. 161 */ 162 public static boolean isSpace(final TwoFacedObject tfo) { 163 return SPACE.equals(tfo.toString()); 164 } 165 166 /** 167 * @return Current toolbar contents as action IDs mapped to labels. 168 */ 169 private List<TwoFacedObject> getCurrentToolbar() { 170 171 List<String> currentIcons = uiManager.getCachedButtons(); 172 IdvActions allActions = uiManager.getCachedActions(); 173 List<TwoFacedObject> icons = new ArrayList<>(currentIcons.size()); 174 for (String actionId : currentIcons) { 175 TwoFacedObject tfo; 176 if (actionId != null) { 177 String desc = allActions.getAttributeForAction(actionId, ActionAttribute.DESCRIPTION); 178 if (desc == null) { 179 desc = "No description associated with action \""+actionId+"\""; 180 } 181 tfo = new TwoFacedObject(desc, actionId); 182 } else { 183 tfo = new TwoFacedObject(SPACE, SPACE + (spaceCount++)); 184 } 185 icons.add(tfo); 186 } 187 return icons; 188 } 189 190 /** 191 * Returns a {@link List} of {@link TwoFacedObject}s containing all of the 192 * actions known to McIDAS-V. 193 */ 194 private List<TwoFacedObject> getAllActions() { 195 IdvActions allActions = uiManager.getCachedActions(); 196 List<String> actionIds = allActions.getAttributes(ActionAttribute.ID); 197 List<TwoFacedObject> actions = new ArrayList<>(actionIds.size()); 198 for (String actionId : actionIds) { 199 String label = allActions.getAttributeForAction(actionId, ActionAttribute.DESCRIPTION); 200 if (label == null) { 201 label = actionId; 202 } 203 actions.add(new TwoFacedObject(label, actionId)); 204 } 205 return actions; 206 } 207 208 /** 209 * Returns the {@link TwoListPanel} being used to store the lists of 210 * available and selected actions. 211 */ 212 public TwoListPanel getTLP() { 213 return twoListPanel; 214 } 215 216 /** 217 * Returns the {@link JComponent} that contains all of the toolbar editor's 218 * UI components. 219 */ 220 public JComponent getContents() { 221 return contents; 222 } 223 224 /** 225 * Initializes the editor window contents. 226 */ 227 private void init() { 228 List<TwoFacedObject> currentIcons = getCurrentToolbar(); 229 List<TwoFacedObject> actions = sortTwoFaced(getAllActions()); 230 231 JButton addSpaceButton = new JButton("Add space"); 232 addSpaceButton.setActionCommand(CMD_ADDSPACE); 233 addSpaceButton.addActionListener(this); 234 235 JButton reloadButton = new JButton(CMD_RELOAD); 236 reloadButton.setActionCommand(CMD_RELOAD); 237 reloadButton.addActionListener(this); 238 239 JButton export1Button = new JButton(CMD_EXPORTPLUGIN); 240 export1Button.setToolTipText(TT_EXPORT_SELECT); 241 export1Button.setActionCommand(CMD_EXPORTPLUGIN); 242 export1Button.addActionListener(this); 243 244 JButton export2Button = new JButton(CMD_EXPORTMENUPLUGIN); 245 export2Button.setToolTipText(TT_EXPORT_SELECTMENU); 246 export2Button.setActionCommand(CMD_EXPORTMENUPLUGIN); 247 export2Button.addActionListener(this); 248 249 List<JComponent> buttons = new ArrayList<>(12); 250 buttons.add(new JLabel(" ")); 251 buttons.add(addSpaceButton); 252 buttons.add(reloadButton); 253 buttons.add(new JLabel(" ")); 254 buttons.add(export1Button); 255 buttons.add(export2Button); 256 257 JPanel extra = GuiUtils.vbox(buttons); 258 259 twoListPanel = 260 new TwoListPanel(actions, "Actions", currentIcons, "Toolbar", extra); 261 262 ListCellRenderer renderer = new IconCellRenderer(this); 263 twoListPanel.getToList().setCellRenderer(renderer); 264 twoListPanel.getFromList().setCellRenderer(renderer); 265 266 contents = GuiUtils.centerBottom(twoListPanel, new JLabel(" ")); 267 } 268 269 /** 270 * Export the selected actions as a menu to the plugin manager. 271 * 272 * @param tfos selected actions 273 */ 274 private void doExportToMenu(List<Object> tfos) { 275 if (menuNameFld == null) { 276 menuNameFld = new JTextField("", 10); 277 278 Map<String, JMenu> menuIds = uiManager.getMenuIds(); 279 280 Vector<TwoFacedObject> menuIdItems = new Vector<>(); 281 menuIdItems.add(new TwoFacedObject("None", null)); 282 283 for (String id : menuIds.keySet()) { 284 JMenu menu = menuIds.get(id); 285 menuIdItems.add(new TwoFacedObject(menu.getText(), id)); 286 } 287 288 menuIdBox = new JComboBox(menuIdItems); 289 menuOverwriteCbx = new JCheckBox("Overwrite", false); 290 menuOverwriteCbx.setToolTipText(TT_OVERWRITE); 291 } 292 293 GuiUtils.tmpInsets = GuiUtils.INSETS_5; 294 JComponent dialogContents = GuiUtils.doLayout(new Component[] { 295 GuiUtils.rLabel("Menu Name:"), 296 menuNameFld, 297 GuiUtils.rLabel("Add to Menu:"), 298 GuiUtils.left( 299 GuiUtils.hbox( 300 menuIdBox, 301 menuOverwriteCbx)) }, 2, 302 GuiUtils.WT_NY, 303 GuiUtils.WT_N); 304 PluginManager pluginManager = uiManager.getIdv().getPluginManager(); 305 while (true) { 306 if (!GuiUtils.askOkCancel(MENU_PLUGINEXPORT, dialogContents)) { 307 return; 308 } 309 310 String menuName = menuNameFld.getText().trim(); 311 if (menuName.isEmpty()) { 312 LogUtil.userMessage(MSG_ENTER_NAME); 313 continue; 314 } 315 316 StringBuffer xml = new StringBuffer(); 317 xml.append(XmlUtil.XML_HEADER); 318 String idXml = ""; 319 320 TwoFacedObject menuIdTfo = 321 (TwoFacedObject)menuIdBox.getSelectedItem(); 322 323 if (menuIdTfo.getId() != null) { 324 idXml = XmlUtil.attr("id", menuIdTfo.getId().toString()); 325 if (menuOverwriteCbx.isSelected()) 326 idXml = idXml + XmlUtil.attr("replace", "true"); 327 } 328 329 xml.append("<menus>\n"); 330 xml.append("<menu label=\"" + menuName + "\" " + idXml + ">\n"); 331 for (int i = 0; i < tfos.size(); i++) { 332 TwoFacedObject tfo = (TwoFacedObject)tfos.get(i); 333 if (isSpace(tfo)) { 334 xml.append("<separator/>\n"); 335 } else { 336 xml.append( 337 XmlUtil.tag( 338 "menuitem", 339 XmlUtil.attrs( 340 "label", tfo.toString(), "action", 341 "action:" + tfo.getId().toString()))); 342 } 343 } 344 xml.append("</menu></menus>\n"); 345 pluginManager.addText(xml.toString(), "menubar.xml"); 346 return; 347 } 348 } 349 350 /** 351 * Export the actions 352 * 353 * @param tfos the actions 354 */ 355 private void doExport(List<Object> tfos) { 356 StringBuffer content = new StringBuffer(); 357 for (int i = 0; i < tfos.size(); i++) { 358 TwoFacedObject tfo = (TwoFacedObject)tfos.get(i); 359 if (tfo.toString().equals(SPACE)) { 360 content.append("<filler/>\n"); 361 } else { 362 content.append( 363 XmlUtil.tag( 364 "button", 365 XmlUtil.attr( 366 "action", "action:" + tfo.getId().toString()))); 367 } 368 } 369 StringBuffer xml = new StringBuffer(); 370 xml.append(XmlUtil.XML_HEADER); 371 xml.append( 372 XmlUtil.tag( 373 "panel", 374 XmlUtil.attrs("layout", "flow", "margin", "4", "vspace", "0") 375 + XmlUtil.attrs( 376 "hspace", "2", "i:space", "2", "i:width", 377 "5"), content.toString())); 378 LogUtil.userMessage( 379 "Note, if a user has changed their toolbar the plugin toolbar will be ignored"); 380 uiManager.getIdv().getPluginManager().addText(xml.toString(), 381 "toolbar.xml"); 382 } 383 384 /** 385 * Handles events such as exporting plugins, reloading contents, and adding 386 * spaces. 387 * 388 * @param ae The event that invoked this method. 389 */ 390 public void actionPerformed(ActionEvent ae) { 391 String c = ae.getActionCommand(); 392 if (CMD_EXPORTMENUPLUGIN.equals(c) || CMD_EXPORTPLUGIN.equals(c)) { 393 List<Object> tfos = twoListPanel.getToList().getSelectedValuesList(); 394 if (tfos.isEmpty()) { 395 LogUtil.userErrorMessage(MSG_SELECT_ENTRIES); 396 } else if (CMD_EXPORTMENUPLUGIN.equals(c)) { 397 doExportToMenu(tfos); 398 } else { 399 doExport(tfos); 400 } 401 } else if (CMD_RELOAD.equals(c)) { 402 twoListPanel.reload(); 403 } else if (CMD_ADDSPACE.equals(c)) { 404 twoListPanel.insertEntry( 405 new TwoFacedObject(SPACE, SPACE+(spaceCount++))); 406 } 407 } 408 409 /** 410 * Has {@code twoListPanel} been changed? 411 * 412 * @return {@code true} if there have been changes, {@code false} 413 * otherwise. 414 */ 415 public boolean anyChanges() { 416 return twoListPanel.getChanged(); 417 } 418 419 /** 420 * Writes out the toolbar xml. 421 */ 422 public void doApply() { 423 Document doc = resources.getWritableDocument("<panel/>"); 424 Element root = resources.getWritableRoot("<panel/>"); 425 root.setAttribute(XmlUi.ATTR_LAYOUT, XmlUi.LAYOUT_FLOW); 426 root.setAttribute(XmlUi.ATTR_MARGIN, "4"); 427 root.setAttribute(XmlUi.ATTR_VSPACE, "0"); 428 root.setAttribute(XmlUi.ATTR_HSPACE, "2"); 429 root.setAttribute(XmlUi.inheritName(XmlUi.ATTR_SPACE), "2"); 430 root.setAttribute(XmlUi.inheritName(XmlUi.ATTR_WIDTH), "5"); 431 432 XmlUtil.removeChildren(root); 433 List<TwoFacedObject> icons = twoListPanel.getCurrentEntries(); 434 for (TwoFacedObject tfo : icons) { 435 Element element; 436 if (isSpace(tfo)) { 437 element = doc.createElement(XmlUi.TAG_FILLER); 438 element.setAttribute(XmlUi.ATTR_WIDTH, "5"); 439 } else { 440 element = doc.createElement(XmlUi.TAG_BUTTON); 441 element.setAttribute(XmlUi.ATTR_ACTION, 442 "action:" + tfo.getId().toString()); 443 } 444 root.appendChild(element); 445 } 446 try { 447 resources.writeWritable(); 448 } catch (Exception exc) { 449 LogUtil.logException("Writing toolbar", exc); 450 } 451 } 452 453 /** 454 * <p> 455 * Sorts a {@link List} of 456 * {@link TwoFacedObject TwoFacedObjects} by label. Case is ignored. 457 * </p> 458 * 459 * @param objs The list that needs some sortin' out. 460 * 461 * @return The sorted contents of {@code objs}. 462 */ 463 private List<TwoFacedObject> sortTwoFaced(final List<TwoFacedObject> objs) { 464 Comparator<TwoFacedObject> comp = new Comparator<TwoFacedObject>() { 465 public int compare(final TwoFacedObject a, final TwoFacedObject b) { 466 return ((String)a.getLabel()).compareToIgnoreCase((String)b.getLabel()); 467 } 468 }; 469 470 List<TwoFacedObject> reordered = new ArrayList<TwoFacedObject>(objs); 471 Collections.sort(reordered, comp); 472 return reordered; 473 } 474 475 /** 476 * Renders a toolbar action and its icon within the {@link TwoListPanel}'s 477 * {@link JList}s. 478 */ 479 private static class IconCellRenderer implements ListCellRenderer { 480 481 /** Icon that represents spaces in the current toolbar actions. */ 482 private static final Icon SPACE_ICON = 483 new SpaceIcon(McvToolbarEditor.ICON_SIZE); 484 485 /** Used to capture the normal cell renderer behaviors. */ 486 private DefaultListCellRenderer defaultRenderer = 487 new DefaultListCellRenderer(); 488 489 /** Used to determine the action ID to icon associations. */ 490 private McvToolbarEditor editor; 491 492 /** 493 * Associates this renderer with the {@link McvToolbarEditor} that 494 * created it. 495 * 496 * @param editor Toolbar editor that contains relevant action ID to 497 * icon mapping. 498 * 499 * @throws NullPointerException if a null McvToolbarEditor was given. 500 */ 501 public IconCellRenderer(final McvToolbarEditor editor) { 502 if (editor == null) { 503 throw new NullPointerException("Toolbar editor cannot be null"); 504 } 505 this.editor = editor; 506 } 507 508 // draws the icon associated with the action ID in value next to the 509 // text label. 510 public Component getListCellRendererComponent(JList list, Object value, 511 int index, boolean isSelected, boolean cellHasFocus) 512 { 513 JLabel renderer = 514 (JLabel)defaultRenderer.getListCellRendererComponent(list, 515 value, index, isSelected, cellHasFocus); 516 517 if (value instanceof TwoFacedObject) { 518 TwoFacedObject tfo = (TwoFacedObject)value; 519 String text = (String)tfo.getLabel(); 520 Icon icon; 521 if (!isSpace(tfo)) { 522 icon = editor.getActionIcon((String)tfo.getId()); 523 } else { 524 icon = SPACE_ICON; 525 } 526 renderer.setIcon(icon); 527 renderer.setText(text); 528 } 529 return renderer; 530 } 531 } 532 533 /** 534 * {@code SpaceIcon} is a class that represents a {@literal "space"} entry 535 * in the {@link TwoListPanel} that holds the current toolbar actions. 536 * 537 * <p>Probably only of use in {@link IconCellRenderer}. 538 */ 539 private static class SpaceIcon implements Icon { 540 541 /** {@code dimension * dimension} is the size of the icon. */ 542 private final int dimension; 543 544 /** 545 * Creates a blank, square icon whose dimensions are {@code dimension} 546 * 547 * @param dimension Icon dimensions. 548 * 549 * @throws IllegalArgumentException if dimension is less than or equal 550 * zero. 551 */ 552 public SpaceIcon(final int dimension) { 553 if (dimension <= 0) { 554 throw new IllegalArgumentException("Dimension must be a positive integer"); 555 } 556 this.dimension = dimension; 557 } 558 559 public int getIconHeight() { 560 return dimension; 561 } 562 563 public int getIconWidth() { 564 return dimension; 565 } 566 567 public void paintIcon(Component c, Graphics g, int x, int y) { 568 g.setColor(new Color(255, 255, 255, 0)); 569 g.drawRect(0, 0, dimension, dimension); 570 } 571 } 572} 573