001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2025
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 https://www.gnu.org/licenses/.
027 */
028
029package edu.wisc.ssec.mcidasv.ui;
030
031import static edu.wisc.ssec.mcidasv.util.McVGuiUtils.getAllComponentHolders;
032import static edu.wisc.ssec.mcidasv.util.McVGuiUtils.getComponentHolders;
033import static edu.wisc.ssec.mcidasv.util.McVGuiUtils.getWindowForHolder;
034
035import java.awt.*;
036import java.util.List;
037
038import javax.swing.JComponent;
039
040import org.w3c.dom.Document;
041import org.w3c.dom.Element;
042
043import ucar.unidata.idv.IntegratedDataViewer;
044import ucar.unidata.idv.ui.IdvComponentHolder;
045import ucar.unidata.idv.ui.IdvUIManager;
046import ucar.unidata.idv.ui.IdvWindow;
047import ucar.unidata.idv.ui.IdvXmlUi;
048import ucar.unidata.idv.ViewManager;
049import ucar.unidata.util.WrapperException;
050import ucar.unidata.xml.XmlUtil;
051
052/**
053 * McIDAS-V needs its own ComponentHolder merely to associate ViewManagers with
054 * their parent ComponentHolders. This association is later used in
055 * McIDASVViewPanel to create a "hierarchical name" for each ViewManager.
056 * 
057 * <p>
058 * Instead of having something like "Panel 1" appearing in the layer controls,
059 * we now have {@literal "ComponentHolder Name>Panel 1"}.</p>
060 *
061 * <p>
062 * Note: ComponentHolder names always double as tab names! McIDAS-V also
063 * intercepts ComponentHolder renaming and updates the layer controls instantly.
064 * </p>
065 */
066public class McvComponentHolder extends IdvComponentHolder {
067
068    /** IDV friendly description of a dynamic XML skin. */
069    public static final String CATEGORY_DESCRIPTION = "UI Skin";
070
071    /** Used to distinguish a dynamic skin from other things. */
072    public static final String TYPE_DYNAMIC_SKIN = "dynamicskin";
073    
074//    private Map<String, ViewManager> dynamicViewManagers = new HashMap<String, ViewManager>();
075
076    /** Kept around to avoid annoying casting. */
077    private UIManager uiManager;
078
079    private JComponent cached = null;
080
081    /**
082     * Default constructor for serialization.
083     */
084    public McvComponentHolder() {
085    }
086
087    /**
088     * Fairly typical constructor.
089     * 
090     * @param idv Reference to the main IDV object.
091     * @param obj object being held in this component holder.
092     */
093    public McvComponentHolder(IntegratedDataViewer idv, Object obj) {
094        super(idv, obj);
095        uiManager = (UIManager)idv.getIdvUIManager();
096    }
097
098    /**
099     * Overridden so that we can (one day) do the required extra work to write
100     * out the XML for this skin.
101     * 
102     * @param doc Parent document we'll use for XML generation.
103     * 
104     * @return XML representation of what is being held.
105     */
106    @Override public Element createXmlNode(Document doc) {
107        if (!getType().equals(TYPE_DYNAMIC_SKIN)) {
108            return super.createXmlNode(doc);
109        }
110
111        // keep in mind that the IDV expects that we're holding a path
112        // to a skin... I don't think that this will work how you want it...
113        // TODO: investigate this!
114        Element node = doc.createElement(IdvUIManager.COMP_COMPONENT_SKIN);
115        node.setAttribute("url", getObject().toString());
116        return node;
117    }
118
119    /**
120     * Overridden so that McV can do the required extra work if this holder is
121     * holding a dynamic XML skin.
122     * 
123     * @return Contents of this holder as a UI component.
124     */
125    @Override public JComponent doMakeContents() {
126        JComponent contents;
127        if (!getType().equals(TYPE_DYNAMIC_SKIN)) {
128            contents = super.doMakeContents();
129        } else {
130            contents = makeDynamicSkin();
131        }
132//        contents.addComponentListener(new ComponentListener() {
133//            @Override public void componentHidden(ComponentEvent e) {
134//                logger.trace("component hidden");
135//                GuiUtils.toggleHeavyWeightComponents(contents, false);
136//            }
137//            @Override public void componentShown(ComponentEvent e) {
138//                logger.trace("component shown");
139//                GuiUtils.toggleHeavyWeightComponents(contents, false);
140//            }
141//            @Override public void componentMoved(ComponentEvent e) {}
142//            @Override public void componentResized(ComponentEvent e) {}
143//        });
144        return contents;
145    }
146
147    /**
148     * Lets the IDV take care of the details, but does null out the local
149     * reference to the UIManager.
150     */
151    @Override public void doRemove() {
152        super.doRemove();
153        uiManager = null;
154    }
155
156    /**
157     * Handles the user attempting to remove this {@code ComponentHolder}.
158     *
159     * <p>Overridden in McIDAS-V to intercept what happens when this holder is
160     * either the last tab in the current window, or the last tab in the application
161     * session.</p>
162     *
163     * <p>If either of the above are true, we hand control off to {@link IdvWindow#doClose()},
164     * which knows when to close the current window vs the entire application.</p>
165     *
166     * <p>Otherwise, we proceed as normal using {@link IdvComponentHolder#removeDisplayComponent()}.</p>
167     *
168     * @return {@code true} if the user decided to close the tab, {@code false} otherwise.
169     */
170    @Override public boolean removeDisplayComponent() {
171        IdvWindow currentWindow = getWindowForHolder(this);
172        List<IdvComponentHolder> localTabs = getComponentHolders(currentWindow);
173        List<IdvComponentHolder> allTabs = getAllComponentHolders();
174        if (allTabs.size() == 1 || localTabs.size() == 1) {
175            // current holder is either the last tab in the app,
176            // or merely the last tab in current window.
177            return currentWindow.doClose(true);
178        } else {
179            // we only need to worry about closing the current tab
180            return super.removeDisplayComponent();
181        }
182    }
183
184    /**
185     * Overridden so that McV can return a more accurate category if this holder
186     * is holding a dynamic skin.
187     * 
188     * @return Category name for the type of thing we're holding.
189     */
190    @Override public String getCategory() {
191        if (!getType().equals(TYPE_DYNAMIC_SKIN)) {
192            return super.getCategory();
193        }
194        return CATEGORY_DESCRIPTION;
195    }
196
197    /**
198     * Overridden so that McV can return a more accurate description if this
199     * holder is holding a dynamic skin.
200     * 
201     * @return The description of what is being held.
202     */
203    @Override public String getTypeName() {
204        if (!getType().equals(TYPE_DYNAMIC_SKIN)) {
205            return super.getTypeName();
206        }
207        return CATEGORY_DESCRIPTION;
208    }
209
210    /**
211     * If the object being held in this component holder is a skin, calling this
212     * method will create a component based upon the skin.
213     * 
214     * <p>
215     * Overridden so that McV can tell the UIManager to associate the skin's
216     * ViewManagers with this component holder. That association is used to
217     * build the hierarchical names in the ViewPanel.
218     * </p>
219     * 
220     * @return The component represented by this holder's skin.
221     */
222    @Override protected JComponent makeSkin() {
223        JComponent comp = super.makeSkin();
224
225        // let's hope that *getViewManagers* only gives us a list of 
226        // ViewManagers
227        @SuppressWarnings("unchecked")
228        List<ViewManager> vms = getViewManagers();
229        if (vms != null) {
230            for (int i = 0; i < vms.size(); i++) {
231                uiManager.setViewManagerHolder(vms.get(i), this);
232                uiManager.getViewPanel().viewManagerChanged(vms.get(i));
233            }
234        }
235        return comp;
236    }
237
238    /**
239     * Mostly used to ensure that the local reference to the UI manager is valid
240     * when deserializing.
241     * 
242     * @param idv Main IDV reference!
243     */
244    @Override
245    public void setIdv(IntegratedDataViewer idv) {
246        super.setIdv(idv);
247        uiManager = (UIManager)idv.getIdvUIManager();
248    }
249
250    /**
251     * Set the name of this component holder to the contents of {@code value}.
252     * 
253     * <p>
254     * Overridden so that McV can tell the ViewPanel to update upon a name
255     * change.
256     * </p>
257     * 
258     * @param value New name of this component holder.
259     */
260    @Override public void setName(String value) {
261        super.setName(value);
262
263        // let's hope that *getViewManagers* only gives us a list of 
264        // ViewManagers
265        @SuppressWarnings("unchecked")
266        List<ViewManager> vms = getViewManagers();
267        if (vms != null) {
268            for (ViewManager vm : vms) {
269                uiManager.getViewPanel().viewManagerChanged(vm);
270            }
271        }
272    }
273
274    /**
275     * Build the UI component using the XML skin contained by this holder.
276     * 
277     * @return UI Component specified by the skin contained in this holder.
278     */
279    public JComponent makeDynamicSkin() {
280        if (cached != null) {
281            return cached;
282        }
283        try {
284            Element root = XmlUtil.getRoot((String) getObject());
285
286            IdvXmlUi ui = uiManager.doMakeIdvXmlUi(null, getViewManagers(),
287                    root);
288
289            // look for any "embedded" ViewManagers.
290            Element startNode = XmlUtil.findElement(root, null, "embeddednode",
291                    "true");
292            if (startNode != null) {
293                ui.setStartNode(startNode);
294            }
295
296            JComponent contents = (JComponent)ui.getContents();
297            setViewManagers(ui.getViewManagers());
298
299            cached = contents;
300            return contents;
301        } catch (Exception e) {
302            throw new WrapperException(e);
303        }
304    }
305
306    /**
307     * Tell this component holder's component group that the tab corresponding
308     * to this holder should become the active tab.
309     */
310    public void setAsActiveTab() {
311        McvComponentGroup parent = (McvComponentGroup)getParent();
312        if (parent != null) {
313            parent.setActiveComponentHolder(this);
314        }
315    }
316}