001 /* 002 * This file is part of McIDAS-V 003 * 004 * Copyright 2007-2013 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 029 package edu.wisc.ssec.mcidasv.ui; 030 031 import java.awt.Color; 032 import java.awt.Component; 033 import java.awt.Graphics; 034 import java.awt.event.ActionEvent; 035 import java.awt.event.ActionListener; 036 import java.util.ArrayList; 037 import java.util.Collections; 038 import java.util.Comparator; 039 import java.util.List; 040 import java.util.Map; 041 import java.util.Vector; 042 043 import javax.swing.DefaultListCellRenderer; 044 import javax.swing.Icon; 045 import javax.swing.JButton; 046 import javax.swing.JCheckBox; 047 import javax.swing.JComboBox; 048 import javax.swing.JComponent; 049 import javax.swing.JLabel; 050 import javax.swing.JList; 051 import javax.swing.JMenu; 052 import javax.swing.JPanel; 053 import javax.swing.JTextField; 054 import javax.swing.ListCellRenderer; 055 056 import org.w3c.dom.Document; 057 import org.w3c.dom.Element; 058 059 import edu.wisc.ssec.mcidasv.ui.UIManager.ActionAttribute; 060 import edu.wisc.ssec.mcidasv.ui.UIManager.IdvActions; 061 062 import ucar.unidata.idv.IdvResourceManager; 063 import ucar.unidata.idv.PluginManager; 064 import ucar.unidata.ui.TwoListPanel; 065 import ucar.unidata.ui.XmlUi; 066 import ucar.unidata.util.GuiUtils; 067 import ucar.unidata.util.LogUtil; 068 import ucar.unidata.util.TwoFacedObject; 069 import ucar.unidata.xml.XmlResourceCollection; 070 import ucar.unidata.xml.XmlUtil; 071 072 public 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 tfo.toString().equals(SPACE); 164 } 165 166 /** 167 * @return Current toolbar contents as action IDs mapped to labels. 168 */ 169 private List<TwoFacedObject> getCurrentToolbar() { 170 List<TwoFacedObject> icons = new ArrayList<TwoFacedObject>(); 171 List<String> currentIcons = uiManager.getCachedButtons(); 172 IdvActions allActions = uiManager.getCachedActions(); 173 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 tfo = new TwoFacedObject(desc, actionId); 181 } else { 182 tfo = new TwoFacedObject(SPACE, SPACE + (spaceCount++)); 183 } 184 icons.add(tfo); 185 } 186 return icons; 187 } 188 189 /** 190 * Returns a {@link List} of {@link TwoFacedObject}s containing all of the 191 * actions known to McIDAS-V. 192 */ 193 private List<TwoFacedObject> getAllActions() { 194 IdvActions allActions = uiManager.getCachedActions(); 195 List<TwoFacedObject> actions = new ArrayList<TwoFacedObject>(); 196 197 List<String> actionIds = allActions.getAttributes(ActionAttribute.ID); 198 for (String actionId : actionIds) { 199 String label = allActions.getAttributeForAction(actionId, ActionAttribute.DESCRIPTION); 200 if (label == null) 201 label = actionId; 202 actions.add(new TwoFacedObject(label, actionId)); 203 } 204 return actions; 205 } 206 207 /** 208 * Returns the {@link TwoListPanel} being used to store 209 * the lists of available and selected actions. 210 */ 211 public TwoListPanel getTLP() { 212 return twoListPanel; 213 } 214 215 /** 216 * Returns the {@link JComponent} that contains all of the toolbar editor's 217 * UI components. 218 */ 219 public JComponent getContents() { 220 return contents; 221 } 222 223 /** 224 * Initializes the editor window contents. 225 */ 226 private void init() { 227 List<TwoFacedObject> currentIcons = getCurrentToolbar(); 228 List<TwoFacedObject> actions = sortTwoFaced(getAllActions()); 229 230 JButton addSpaceButton = new JButton("Add space"); 231 addSpaceButton.setActionCommand(CMD_ADDSPACE); 232 addSpaceButton.addActionListener(this); 233 234 JButton reloadButton = new JButton(CMD_RELOAD); 235 reloadButton.setActionCommand(CMD_RELOAD); 236 reloadButton.addActionListener(this); 237 238 JButton export1Button = new JButton(CMD_EXPORTPLUGIN); 239 export1Button.setToolTipText(TT_EXPORT_SELECT); 240 export1Button.setActionCommand(CMD_EXPORTPLUGIN); 241 export1Button.addActionListener(this); 242 243 JButton export2Button = new JButton(CMD_EXPORTMENUPLUGIN); 244 export2Button.setToolTipText(TT_EXPORT_SELECTMENU); 245 export2Button.setActionCommand(CMD_EXPORTMENUPLUGIN); 246 export2Button.addActionListener(this); 247 248 List<JComponent> buttons = new ArrayList<JComponent>(); 249 buttons.add(new JLabel(" ")); 250 buttons.add(addSpaceButton); 251 buttons.add(reloadButton); 252 buttons.add(new JLabel(" ")); 253 buttons.add(export1Button); 254 buttons.add(export2Button); 255 256 JPanel extra = GuiUtils.vbox(buttons); 257 258 twoListPanel = 259 new TwoListPanel(actions, "Actions", currentIcons, "Toolbar", extra); 260 261 ListCellRenderer renderer = new IconCellRenderer(this); 262 twoListPanel.getToList().setCellRenderer(renderer); 263 twoListPanel.getFromList().setCellRenderer(renderer); 264 265 contents = GuiUtils.centerBottom(twoListPanel, new JLabel(" ")); 266 } 267 268 /** 269 * Export the selected actions as a menu to the plugin manager. 270 * 271 * @param tfos selected actions 272 */ 273 private void doExportToMenu(Object[] tfos) { 274 if (menuNameFld == null) { 275 menuNameFld = new JTextField("", 10); 276 277 Map<String, JMenu> menuIds = uiManager.getMenuIds(); 278 279 Vector<TwoFacedObject> menuIdItems = new Vector<TwoFacedObject>(); 280 menuIdItems.add(new TwoFacedObject("None", null)); 281 282 for (String id : menuIds.keySet()) { 283 JMenu menu = menuIds.get(id); 284 menuIdItems.add(new TwoFacedObject(menu.getText(), id)); 285 } 286 287 menuIdBox = new JComboBox(menuIdItems); 288 menuOverwriteCbx = new JCheckBox("Overwrite", false); 289 menuOverwriteCbx.setToolTipText(TT_OVERWRITE); 290 } 291 292 GuiUtils.tmpInsets = GuiUtils.INSETS_5; 293 JComponent dialogContents = GuiUtils.doLayout(new Component[] { 294 GuiUtils.rLabel("Menu Name:"), 295 menuNameFld, 296 GuiUtils.rLabel("Add to Menu:"), 297 GuiUtils.left( 298 GuiUtils.hbox( 299 menuIdBox, 300 menuOverwriteCbx)) }, 2, 301 GuiUtils.WT_NY, 302 GuiUtils.WT_N); 303 PluginManager pluginManager = uiManager.getIdv().getPluginManager(); 304 while (true) { 305 if (!GuiUtils.askOkCancel(MENU_PLUGINEXPORT, dialogContents)) { 306 return; 307 } 308 309 String menuName = menuNameFld.getText().trim(); 310 if (menuName.length() == 0) { 311 LogUtil.userMessage(MSG_ENTER_NAME); 312 continue; 313 } 314 315 StringBuffer xml = new StringBuffer(); 316 xml.append(XmlUtil.XML_HEADER); 317 String idXml = ""; 318 319 TwoFacedObject menuIdTfo = 320 (TwoFacedObject)menuIdBox.getSelectedItem(); 321 322 if (menuIdTfo.getId() != null) { 323 idXml = XmlUtil.attr("id", menuIdTfo.getId().toString()); 324 if (menuOverwriteCbx.isSelected()) 325 idXml = idXml + XmlUtil.attr("replace", "true"); 326 } 327 328 xml.append("<menus>\n"); 329 xml.append("<menu label=\"" + menuName + "\" " + idXml + ">\n"); 330 for (int i = 0; i < tfos.length; i++) { 331 TwoFacedObject tfo = (TwoFacedObject)tfos[i]; 332 if (isSpace(tfo)) { 333 xml.append("<separator/>\n"); 334 } else { 335 xml.append( 336 XmlUtil.tag( 337 "menuitem", 338 XmlUtil.attrs( 339 "label", tfo.toString(), "action", 340 "action:" + tfo.getId().toString()))); 341 } 342 } 343 xml.append("</menu></menus>\n"); 344 pluginManager.addText(xml.toString(), "menubar.xml"); 345 return; 346 } 347 } 348 349 /** 350 * Export the actions 351 * 352 * @param tfos the actions 353 */ 354 private void doExport(Object[] tfos) { 355 StringBuffer content = new StringBuffer(); 356 for (int i = 0; i < tfos.length; i++) { 357 TwoFacedObject tfo = (TwoFacedObject) tfos[i]; 358 if (tfo.toString().equals(SPACE)) { 359 content.append("<filler/>\n"); 360 } else { 361 content.append( 362 XmlUtil.tag( 363 "button", 364 XmlUtil.attr( 365 "action", "action:" + tfo.getId().toString()))); 366 } 367 } 368 StringBuffer xml = new StringBuffer(); 369 xml.append(XmlUtil.XML_HEADER); 370 xml.append( 371 XmlUtil.tag( 372 "panel", 373 XmlUtil.attrs("layout", "flow", "margin", "4", "vspace", "0") 374 + XmlUtil.attrs( 375 "hspace", "2", "i:space", "2", "i:width", 376 "5"), content.toString())); 377 LogUtil.userMessage( 378 "Note, if a user has changed their toolbar the plugin toolbar will be ignored"); 379 uiManager.getIdv().getPluginManager().addText(xml.toString(), 380 "toolbar.xml"); 381 } 382 383 /** 384 * Handles events such as exporting plugins, reloading contents, and adding 385 * spaces. 386 * 387 * @param ae The event that invoked this method. 388 */ 389 public void actionPerformed(ActionEvent ae) { 390 String c = ae.getActionCommand(); 391 if (c.equals(CMD_EXPORTMENUPLUGIN) || c.equals(CMD_EXPORTPLUGIN)) { 392 Object[] tfos = twoListPanel.getToList().getSelectedValues(); 393 if (tfos.length == 0) 394 LogUtil.userMessage(MSG_SELECT_ENTRIES); 395 else if (c.equals(CMD_EXPORTMENUPLUGIN)) 396 doExportToMenu(tfos); 397 else 398 doExport(tfos); 399 } 400 else if (c.equals(CMD_RELOAD)) { 401 twoListPanel.reload(); 402 } 403 else if (c.equals(CMD_ADDSPACE)) { 404 twoListPanel.insertEntry( 405 new TwoFacedObject(SPACE, SPACE+(spaceCount++))); 406 } 407 } 408 409 /** 410 * Has <code>twoListPanel</code> been changed? 411 * 412 * @return <code>true</code> if there have been changes, <code>false</code> 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}s 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 <tt>objs</tt>. 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 /** Icon that represents spaces in the current toolbar actions. */ 481 private static final Icon SPACE_ICON = 482 new SpaceIcon(McvToolbarEditor.ICON_SIZE); 483 484 /** Used to capture the normal cell renderer behaviors. */ 485 private DefaultListCellRenderer defaultRenderer = 486 new DefaultListCellRenderer(); 487 488 /** Used to determine the action ID to icon associations. */ 489 private McvToolbarEditor editor; 490 491 /** 492 * Associates this renderer with the {@link McvToolbarEditor} that 493 * created it. 494 * 495 * @param editor Toolbar editor that contains relevant action ID to 496 * icon mapping. 497 * 498 * @throws NullPointerException if a null McvToolbarEditor was given. 499 */ 500 public IconCellRenderer(final McvToolbarEditor editor) { 501 if (editor == null) 502 throw new NullPointerException("Toolbar editor cannot be null"); 503 this.editor = editor; 504 } 505 506 // draws the icon associated with the action ID in value next to the 507 // text label. 508 public Component getListCellRendererComponent(JList list, Object value, 509 int index, boolean isSelected, boolean cellHasFocus) 510 { 511 JLabel renderer = 512 (JLabel)defaultRenderer.getListCellRendererComponent(list, 513 value, index, isSelected, cellHasFocus); 514 515 if (value instanceof TwoFacedObject) { 516 TwoFacedObject tfo = (TwoFacedObject)value; 517 String text = (String)tfo.getLabel(); 518 Icon icon; 519 if (!isSpace(tfo)) 520 icon = editor.getActionIcon((String)tfo.getId()); 521 else 522 icon = SPACE_ICON; 523 renderer.setIcon(icon); 524 renderer.setText(text); 525 } 526 return renderer; 527 } 528 } 529 530 /** 531 * {@code SpaceIcon} is a class that represents a {@literal "space"} entry 532 * in the {@link TwoListPanel} that holds the current toolbar actions. 533 * 534 * <p>Probably only of use in {@link IconCellRenderer}. 535 */ 536 private static class SpaceIcon implements Icon { 537 /** {@code dimension * dimension} is the size of the icon. */ 538 private final int dimension; 539 540 /** 541 * Creates a blank, square icon whose dimensions are {@code dimension} 542 * 543 * @param dimension Icon dimensions. 544 * 545 * @throws IllegalArgumentException if dimension is less than or equal 546 * zero. 547 */ 548 public SpaceIcon(final int dimension) { 549 if (dimension <= 0) 550 throw new IllegalArgumentException("Dimension must be a positive integer"); 551 this.dimension = dimension; 552 } 553 554 public int getIconHeight() { return dimension; } 555 public int getIconWidth() { return dimension; } 556 public void paintIcon(Component c, Graphics g, int x, int y) { 557 g.setColor(new Color(255, 255, 255, 0)); 558 g.drawRect(0, 0, dimension, dimension); 559 } 560 } 561 } 562