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 &amp;gt; and &amp;lt; entities to &gt; and &lt;.
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("&gt", ">").replace("&lt;", ">");
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 &quot;dynamic skin&quot; is built with ViewManagers
222         * for each ViewManager in the bundle. The &quot;viewid&quot; 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 &quot;viewid&quot; 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         * &quot;blank&quot; 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 &quot;cache&quot; 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    }