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