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