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