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 &amp;gt; and &amp;lt; entities to &gt; and &lt;.
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("&gt;", ">");
115        html = html.replace("&lt;", "<");
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 &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        } 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 &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     * @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}