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