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.Component;
032 import java.awt.event.ActionEvent;
033 import java.util.HashMap;
034 import java.util.List;
035 import java.util.Map;
036
037 import javax.swing.ImageIcon;
038 import javax.swing.JComponent;
039 import javax.swing.event.HyperlinkEvent;
040 import javax.swing.event.HyperlinkListener;
041 import javax.swing.event.HyperlinkEvent.EventType;
042
043 import org.w3c.dom.Element;
044 import org.w3c.dom.NamedNodeMap;
045 import org.w3c.dom.Node;
046 import org.w3c.dom.NodeList;
047
048 import ucar.unidata.idv.IntegratedDataViewer;
049 import ucar.unidata.idv.ViewManager;
050 import ucar.unidata.idv.ui.IdvComponentGroup;
051 import ucar.unidata.idv.ui.IdvComponentHolder;
052 import ucar.unidata.idv.ui.IdvUIManager;
053 import ucar.unidata.idv.ui.IdvWindow;
054 import ucar.unidata.idv.ui.IdvXmlUi;
055 import ucar.unidata.ui.ComponentHolder;
056 import ucar.unidata.ui.HtmlComponent;
057 import ucar.unidata.util.GuiUtils;
058 import ucar.unidata.util.IOUtil;
059 import ucar.unidata.xml.XmlUtil;
060
061 import 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 {@link McvComponentGroup}s
067 * and {@link McvComponentHolder}s are created, and handle some special
068 * problems that occur when attempting to load bundles that do not contain
069 * component groups.
070 * </p>
071 */
072 @SuppressWarnings("unchecked")
073 public class McIDASVXmlUi extends IdvXmlUi {
074
075 /**
076 * Maps an ID to an {@link Element}.
077 */
078 // private Hashtable<String, Element> idToElement;
079 private Map<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 HashMap<String, Element>();
094 }
095 }
096
097 public McIDASVXmlUi(IdvWindow window, List viewManagers,
098 IntegratedDataViewer idv, Element root)
099 {
100 super(window, viewManagers, idv, root);
101 this.idv = idv;
102 this.window = window;
103 if (idToElement == null) {
104 idToElement = new HashMap<String, Element>();
105 }
106 }
107
108 /**
109 * Convert the &gt; and &lt; entities to > and <.
110 *
111 * @param text The text you'd like to convert.
112 *
113 * @return The converted text!
114 */
115 private static String decodeHtml(String text) {
116 return text.replace(">", ">").replace("<", ">");
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 HashMap<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 }
279 } else if (tagName.equals(TAG_TREEPANEL)) {
280 comp = createTreePanel(node, id);
281 } else {
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 private ViewManager createViewManager(final Element node) {
313 final String viewId = getAttr(node, "viewid", NULLSTRING);
314 ViewManager vm = null;
315 if (viewId != null) {
316 ViewManager old = UIManager.savedViewManagers.remove(viewId);
317 if (old != null) {
318 vm = getViewManager(node);
319 vm.initWith(old);
320 old.destroy();
321 }
322 }
323 return vm;
324 }
325
326 private TreePanel createTreePanel(final Element node, final String id) {
327
328 TreePanel treePanel =
329 new TreePanel(getAttr(node, ATTR_USESPLITPANE, false),
330 getAttr(node, ATTR_TREEWIDTH, -1));
331
332 List<Element> kids = XmlUtil.getListOfElements(node);
333
334 for (Element kid : kids) {
335 Component comp = xmlToUi(kid);
336 if (comp == null) {
337 continue;
338 }
339
340 String label = getAttr(kid, ATTR_TITLE, "");
341
342 ImageIcon icon = getAttr(kid, ATTR_ICON, (ImageIcon)null);
343 String cat = getAttr(kid, ATTR_CATEGORY, (String)null);
344 if (XmlUtil.getAttribute(kid, ATTR_CATEGORYCOMPONENT, false)) {
345 treePanel.addCategoryComponent(cat, (JComponent)comp);
346 } else {
347 treePanel.addComponent((JComponent)comp, cat, label, icon);
348 }
349 }
350 treePanel.closeAll();
351 treePanel.showPersistedSelection();
352 return treePanel;
353 }
354
355 /**
356 * The xml nodes can contain an idref field. If so this returns the
357 * node that that id defines
358 *
359 * @param node node
360 *
361 * @return The node or the referenced node
362 */
363 private Element getReffedNode(Element node) {
364 String idRef = getAttr(node, ATTR_IDREF, NULLSTRING);
365 if (idRef == null) {
366 return node;
367 }
368
369 Element reffedNode = (Element)idToElement.get(idRef);
370 if (reffedNode == null) {
371 throw new IllegalStateException("Could not find idref=" + idRef);
372 }
373
374 // TODO(unidata): Make a new copy of the node
375 // reffedNode = reffedNode.copy ();
376 NamedNodeMap map = node.getAttributes();
377 for (int i = 0; i < map.getLength(); i++) {
378 Node n = map.item(i);
379 if (!n.getNodeName().equals(ATTR_IDREF)) {
380 reffedNode.setAttribute(n.getNodeName(), n.getNodeValue());
381 }
382 }
383 return reffedNode;
384 }
385 }