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.Component; 032import java.awt.event.ActionEvent; 033import java.util.HashMap; 034import java.util.List; 035import java.util.Map; 036 037import javax.swing.ImageIcon; 038import javax.swing.JComponent; 039import javax.swing.event.HyperlinkEvent; 040import javax.swing.event.HyperlinkListener; 041import javax.swing.event.HyperlinkEvent.EventType; 042 043import org.w3c.dom.Element; 044import org.w3c.dom.NamedNodeMap; 045import org.w3c.dom.Node; 046import org.w3c.dom.NodeList; 047 048import ucar.unidata.idv.IntegratedDataViewer; 049import ucar.unidata.idv.ViewManager; 050import ucar.unidata.idv.ui.IdvComponentGroup; 051import ucar.unidata.idv.ui.IdvComponentHolder; 052import ucar.unidata.idv.ui.IdvUIManager; 053import ucar.unidata.idv.ui.IdvWindow; 054import ucar.unidata.idv.ui.IdvXmlUi; 055import ucar.unidata.ui.ComponentHolder; 056import ucar.unidata.ui.HtmlComponent; 057import ucar.unidata.util.GuiUtils; 058import ucar.unidata.util.IOUtil; 059import ucar.unidata.xml.XmlUtil; 060 061import edu.wisc.ssec.mcidasv.util.TreePanel; 062 063/** 064 * <p> 065 * McIDAS-V mostly extends this class to preempt the IDV. McIDAS-V needs to 066 * control some HTML processing, ensure that 067 * {@link McvComponentGroup McvComponentGroups} and 068 * {@link McvComponentHolder McvComponentHolders} are created, and handle some 069 * special problems that occur when attempting to load bundles that do not 070 * contain component groups. 071 * </p> 072 */ 073@SuppressWarnings("unchecked") 074public class McIDASVXmlUi extends IdvXmlUi { 075 076 /** Maps a {@code String} ID to an {@link Element}. */ 077 private Map<String, Element> idToElement; 078 079 /** Avoids unneeded getIdv() calls. */ 080 private IntegratedDataViewer idv; 081 082 /** 083 * Keep around a reference to the window we were built for, useful for 084 * associated component groups with the appropriate window. 085 */ 086 private IdvWindow window; 087 088 public McIDASVXmlUi(IntegratedDataViewer idv, Element root) { 089 super(idv, root); 090 if (idToElement == null) { 091 idToElement = new HashMap<>(); 092 } 093 } 094 095 public McIDASVXmlUi(IdvWindow window, List viewManagers, 096 IntegratedDataViewer idv, Element root) 097 { 098 super(window, viewManagers, idv, root); 099 this.idv = idv; 100 this.window = window; 101 if (idToElement == null) { 102 idToElement = new HashMap<>(); 103 } 104 } 105 106 /** 107 * Convert the &gt; and &lt; entities to > and <. 108 * 109 * @param text The text you'd like to convert. 110 * 111 * @return The converted text! 112 */ 113 private static String decodeHtml(String text) { 114 return text.replace(">", ">").replace("<", ">"); 115 } 116 117 /** 118 * Add the component. 119 * 120 * @param id id 121 * @param component component 122 */ 123 @Override public void addComponent(String id, Element component) { 124 // this needs to be here because even if you create idToElement in the 125 // constructor, this method will get called from 126 // ucar.unidata.xml.XmlUi#initialize(Element) before control has 127 // returned to the McIDASVXmlUi constructor! 128 if (idToElement == null) { 129 idToElement = new HashMap<>(); 130 } 131 super.addComponent(id, component); 132 idToElement.put(id, component); 133 } 134 135 /** 136 * Overridden so that any attempts to generate 137 * {@link IdvComponentGroup IdvComponentGroups} or 138 * {@link IdvComponentHolder IdvComponentHolders} will return the 139 * respective McIDAS-V equivalents. 140 * 141 * <p> 142 * It makes things like the draggable tabs possible. 143 * </p> 144 * 145 * @param node XML representation of the desired component group. 146 * 147 * @return An {@code McvComponentGroup} based upon the contents of {@code node}. 148 */ 149 @Override protected IdvComponentGroup makeComponentGroup(Element node) { 150 McvComponentGroup group = new McvComponentGroup(idv, "", window); 151 group.initWith(node); 152 153 NodeList elements = XmlUtil.getElements(node); 154 for (int i = 0; i < elements.getLength(); i++) { 155 Element child = (Element)elements.item(i); 156 157 String tag = child.getTagName(); 158 159 if (tag.equals(IdvUIManager.COMP_MAPVIEW) 160 || tag.equals(IdvUIManager.COMP_VIEW)) 161 { 162 ViewManager viewManager = getViewManager(child); 163 group.addComponent(new McvComponentHolder(idv, viewManager)); 164 } 165 else if (tag.equals(IdvUIManager.COMP_COMPONENT_CHOOSERS)) { 166 IdvComponentHolder comp = 167 new McvComponentHolder(idv, "choosers"); 168 comp.setType(IdvComponentHolder.TYPE_CHOOSERS); 169 comp.setName(XmlUtil.getAttribute(child, "name", "Choosers")); 170 group.addComponent(comp); 171 } 172 else if (tag.equals(IdvUIManager.COMP_COMPONENT_SKIN)) { 173 IdvComponentHolder comp = new McvComponentHolder(idv, 174 XmlUtil.getAttribute(child, "url")); 175 176 comp.setType(IdvComponentHolder.TYPE_SKIN); 177 comp.setName(XmlUtil.getAttribute(child, "name", "UI")); 178 group.addComponent(comp); 179 } 180 else if (tag.equals(IdvUIManager.COMP_COMPONENT_HTML)) { 181 String text = XmlUtil.getChildText(child); 182 text = new String(XmlUtil.decodeBase64(text.trim())); 183 ComponentHolder comp = new HtmlComponent("Html Text", text); 184 comp.setShowHeader(false); 185 comp.setName(XmlUtil.getAttribute(child, "name", "HTML")); 186 group.addComponent(comp); 187 } 188 else if (tag.equals(IdvUIManager.COMP_DATASELECTOR)) { 189 group.addComponent(new McvComponentHolder(idv, 190 idv.getIdvUIManager().createDataSelector(false, false))); 191 } 192 else if (tag.equals(IdvUIManager.COMP_COMPONENT_GROUP)) { 193 group.addComponent(makeComponentGroup(child)); 194 } 195 else { 196 System.err.println("Unknown component element:" 197 + XmlUtil.toString(child)); 198 } 199 } 200 return group; 201 } 202 203 /** 204 * McIDAS-V overrides this so that it can seize control of some HTML 205 * processing in addition to attempting to associate newly-created 206 * {@link ViewManager ViewManagers} with ViewManagers found in a bundle. 207 * 208 * <p> 209 * The latter is done so that McIDAS-V can load bundles that do not use 210 * component groups. A {@literal "dynamic skin"} is built with ViewManagers 211 * for each ViewManager in the bundle. The {@literal "viewid"} attribute of 212 * the dynamic skin ViewManager is the name of the 213 * {@link ucar.unidata.idv.ViewDescriptor} from the bundled ViewManager. 214 * {@code createViewManager()} is used to actually associate the new 215 * ViewManager with its bundled ViewManager. 216 * </p> 217 * 218 * @param node The XML describing the component to be created. 219 * @param id ID of {@code node}. 220 * 221 * @return The {@link java.awt.Component} described by {@code node}. 222 * 223 * @see edu.wisc.ssec.mcidasv.ui.McIDASVXmlUi#createViewManager(Element) 224 */ 225 @Override public Component createComponent(Element node, String id) { 226 Component comp = null; 227 String tagName = node.getTagName(); 228 if (tagName.equals(TAG_HTML)) { 229 String text = getAttr(node, ATTR_TEXT, NULLSTRING); 230 text = decodeHtml(text); 231 if (text == null) { 232 String url = getAttr(node, ATTR_URL, NULLSTRING); 233 if (url != null) { 234 text = IOUtil.readContents(url, (String)null); 235 } 236 if (text == null) { 237 text = XmlUtil.getChildText(node); 238 } 239 } 240 HyperlinkListener linkListener = new HyperlinkListener() { 241 public void hyperlinkUpdate(HyperlinkEvent e) { 242 if (e.getEventType() != EventType.ACTIVATED) { 243 return; 244 } 245 String url; 246 if (e.getURL() == null) { 247 url = e.getDescription(); 248 } else { 249 url = e.getURL().toString(); 250 } 251 actionPerformed(new ActionEvent(this, 0, url)); 252 } 253 }; 254 Component[] comps = 255 GuiUtils.getHtmlComponent(text, linkListener, getAttr(node, 256 ATTR_WIDTH, 200), getAttr(node, ATTR_HEIGHT, 200)); 257 comp = comps[1]; 258 } else if (tagName.equals(UIManager.COMP_MAPVIEW) 259 || tagName.equals(UIManager.COMP_VIEW)) { 260 261 // if we're creating a VM for a dynamic skin that was created for 262 // a bundle, createViewManager() will return the bundled VM. 263 ViewManager vm = createViewManager(node); 264 if (vm != null) { 265 comp = vm.getContents(); 266 } else { 267 comp = super.createComponent(node, id); 268 } 269 } else if (tagName.equals(TAG_TREEPANEL)) { 270 comp = createTreePanel(node, id); 271 } else { 272 comp = super.createComponent(node, id); 273 } 274 275 return comp; 276 } 277 278 /** 279 * <p> 280 * Attempts to build a {@link ucar.unidata.idv.ViewManager} based upon 281 * {@code node}. If the XML has a {@literal "viewid"} attribute, the 282 * value will be used to search for a ViewManager that has been cached by 283 * the McIDAS-V {@link UIManager}. If the UIManager has a matching 284 * ViewManager, we'll use the cached ViewManager to initialize a 285 * {@literal "blank"} ViewManager. The cached ViewManager is then removed 286 * from the cache and deleted. This method will return {@code null} if 287 * no cached ViewManager was found. 288 * </p> 289 * 290 * <p> 291 * The ViewManager {@literal "cache"} will only contain bundled ViewManagers 292 * that were not held in a component holder. This means that any 293 * ViewManager returned was created for a dynamic skin, but initialized 294 * with the contents of the corresponding bundled ViewManager. 295 * </p> 296 * 297 * @param node XML description of the ViewManager that needs building. 298 * 299 * @return {@code null} if there was no cached ViewManager, otherwise a 300 * {@code ViewManager} that has been initialized with a bundled ViewManager. 301 */ 302 private ViewManager createViewManager(final Element node) { 303 final String viewId = getAttr(node, "viewid", NULLSTRING); 304 ViewManager vm = null; 305 if (viewId != null) { 306 ViewManager old = UIManager.savedViewManagers.remove(viewId); 307 if (old != null) { 308 vm = getViewManager(node); 309 vm.initWith(old); 310 old.destroy(); 311 } 312 } 313 return vm; 314 } 315 316 private TreePanel createTreePanel(final Element node, final String id) { 317 318 TreePanel treePanel = 319 new TreePanel(getAttr(node, ATTR_USESPLITPANE, false), 320 getAttr(node, ATTR_TREEWIDTH, -1)); 321 322 List<Element> kids = XmlUtil.getListOfElements(node); 323 324 for (Element kid : kids) { 325 Component comp = xmlToUi(kid); 326 if (comp == null) { 327 continue; 328 } 329 330 String label = getAttr(kid, ATTR_TITLE, ""); 331 332 ImageIcon icon = getAttr(kid, ATTR_ICON, (ImageIcon)null); 333 String cat = getAttr(kid, ATTR_CATEGORY, (String)null); 334 if (XmlUtil.getAttribute(kid, ATTR_CATEGORYCOMPONENT, false)) { 335 treePanel.addCategoryComponent(cat, (JComponent)comp); 336 } else { 337 treePanel.addComponent((JComponent)comp, cat, label, icon); 338 } 339 } 340 treePanel.closeAll(); 341 treePanel.showPersistedSelection(); 342 return treePanel; 343 } 344 345 /** 346 * The xml nodes can contain an idref field. If so this returns the 347 * node that that id defines 348 * 349 * @param node node 350 * 351 * @return The node or the referenced node 352 */ 353 private Element getReffedNode(Element node) { 354 String idRef = getAttr(node, ATTR_IDREF, NULLSTRING); 355 if (idRef == null) { 356 return node; 357 } 358 359 Element reffedNode = idToElement.get(idRef); 360 if (reffedNode == null) { 361 throw new IllegalStateException("Could not find idref=" + idRef); 362 } 363 364 // TODO(unidata): Make a new copy of the node 365 // reffedNode = reffedNode.copy(); 366 NamedNodeMap map = node.getAttributes(); 367 for (int i = 0; i < map.getLength(); i++) { 368 Node n = map.item(i); 369 if (!n.getNodeName().equals(ATTR_IDREF)) { 370 reffedNode.setAttribute(n.getNodeName(), n.getNodeValue()); 371 } 372 } 373 return reffedNode; 374 } 375}