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