001    /*
002     * $Id: ViewManagerManager.java,v 1.16 2012/02/19 17:35:53 davep 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;
032    
033    import static edu.wisc.ssec.mcidasv.util.CollectionHelpers.newHashSet;
034    
035    import java.util.List;
036    import java.util.Set;
037    import java.util.Stack;
038    
039    import org.slf4j.Logger;
040    import org.slf4j.LoggerFactory;
041    
042    import edu.wisc.ssec.mcidasv.ui.McvComponentHolder;
043    import edu.wisc.ssec.mcidasv.ui.UIManager;
044    
045    import ucar.unidata.idv.IntegratedDataViewer;
046    import ucar.unidata.idv.VMManager;
047    import ucar.unidata.idv.ViewDescriptor;
048    import ucar.unidata.idv.ViewManager;
049    import ucar.unidata.idv.control.DisplayControlImpl;
050    import ucar.unidata.ui.ComponentGroup;
051    import ucar.unidata.ui.ComponentHolder;
052    import ucar.unidata.util.GuiUtils;
053    
054    /**
055     * <p>McIDAS-V needs to manage ViewManagers in a slightly different way than 
056     * the IDV. The key differences between the two are the way previously active 
057     * ViewManagers are ordered and a (hopefully) more consistent way of handling 
058     * active ViewManagers.</p>
059     * 
060     * <p>The IDV only keeps track of the ViewManager used immediately before the 
061     * current one. McV keeps track of the previously active ViewManagers in a 
062     * stack. This mimics window z-ordering and always returns the user to the most
063     * recently active ViewManager upon removal of the active ViewManager.</p>
064     * 
065     * <p>Newly created ViewManagers and their first layer now become the active
066     * ViewManager and layer. If there is only one ViewManager, it is now displayed
067     * as active instead of just implying it. When the active ViewManager is 
068     * removed, the last active ViewManager and its first layer become active.</p>
069     * 
070     * <p><b>A note to the future</b>: McV/IDV supports two notions of active and 
071     * selected ViewManagers. Say you have NxN ViewManagers in a ComponentHolder, 
072     * and you want to share views among some of these ViewManagers. When one of 
073     * these shared ViewManagers is activated, should all of them become the active
074     * ViewManager? We're going to have to work out how to convey which 
075     * ViewManagers are shared and active, and maybe more? Good luck!</p>
076     */
077    // TODO: should accesses to previousVMs should be synchronized?
078    // TODO: should keep track of the ordering of active layers per VM as well.
079    public class ViewManagerManager extends VMManager {
080    
081        private static final Logger logger = LoggerFactory.getLogger(ViewManagerManager.class);
082    
083        /** Whether or not to display debug messages. */
084        private final boolean DEBUG = false;
085    
086        /** The stack that stores the order of previously active ViewManagers. */
087        private final Stack<ViewManager> previousVMs = new Stack<ViewManager>();
088    
089        /** Convenient reference back to the UIManager. */
090        private UIManager uiManager;
091    
092        /**
093         * Yet another constructor.
094         */
095        public ViewManagerManager(IntegratedDataViewer idv) {
096            super(idv);
097            uiManager = (UIManager)getIdvUIManager();
098        }
099    
100        public int getViewManagerCount() {
101            return getViewManagers().size();
102        }
103    
104        /**
105         * Add the new view manager into the list if we don't have
106         * one with the {@link ViewDescriptor} of the new view manager
107         * already.
108         *
109         * @param newViewManager The new view manager
110         */
111        @Override public void addViewManager(ViewManager newViewManager) {
112            super.addViewManager(newViewManager);
113            focusLayerControlsOn(newViewManager, false);
114        }
115    
116        /**
117         * @return Reference to the stack of previously active ViewManagers.
118         */
119        public Stack<ViewManager> getViewManagerOrder() {
120            return previousVMs;
121        }
122    
123        /**
124         * Overridden so that McV can set the active ViewManager even if there is
125         * only one ViewManager. This is just a UI nicety; it'll allow the McV UI
126         * to show the active ViewManager no matter what.
127         * 
128         * @return Always returns true.
129         */
130        @Override public boolean haveMoreThanOneMainViewManager() {
131            return true;
132        }
133    
134        /**
135         * Handles the removal of a ViewManager. McV needs to override this so that
136         * the stack of previously active ViewManagers is ordered properly. McV uses
137         * this method to make the ViewPanel respond immediately to the change.
138         * 
139         * @param viewManager The ViewManager being removed.
140         */
141        @Override public void removeViewManager(ViewManager viewManager) {
142            // the ordering of the stack must be preserved! this is the only chance
143            // to ensure the ordering if the incoming VM is inactive.
144            if (getLastActiveViewManager() != viewManager) {
145                previousVMs.remove(viewManager);
146                inspectStack("removing inactive vm");
147            }
148    
149            // now just sit back and let the IDV and setLastActiveViewManager work
150            // their magic.
151            super.removeViewManager(viewManager);
152    
153            // inform UIManager that the VM needs to be dissociated from its
154            // ComponentHolder.
155            uiManager.removeViewManagerHolder(viewManager);
156    
157            // force the layer controls tabs to layout the remaining components, 
158            // but we don't want to bring it to the front!
159            uiManager.getViewPanel().getContents().validate();
160        }
161    
162        /**
163         * <p>This method is a bit strange. If the given ViewManager is null, then 
164         * the IDV has removed the active ViewManager. McV will use the stack of 
165         * last active ViewManagers to make the last active ViewManager active once 
166         * again.</p>
167         * 
168         * <p>If the given ViewManager is not null, but cannot be found in the stack
169         * of previously active ViewManagers, the IDV has created a new ViewManager 
170         * and McV must push it on the stack.</p>
171         * 
172         * <p>If the given ViewManager is not null and has been found in the stack,
173         * then the user has selected an inactive ViewManager. McV must remove the
174         * ViewManager from the stack and then push it back on top.</p>
175         * 
176         * <p>These steps allow McV to make the behavior of closing tabs a bit more
177         * user-friendly. The user is always returned to whichever ViewManager was
178         * last active.</p>
179         * 
180         * @param vm See above. :(
181         */
182        // TODO: when you start removing the debug stuff, just convert the messages
183        // to comments.
184        @Override public void setLastActiveViewManager(ViewManager vm) {
185            String debugMsg = "created new vm";
186            if (vm != null) {
187                if (previousVMs.search(vm) >= 0) {
188                    debugMsg = "reset active vm";
189                    previousVMs.remove(vm);
190                    focusLayerControlsOn(vm, false);
191                }
192                previousVMs.push(vm);
193            } else {
194                debugMsg = "removed active vm";
195    
196                ViewManager lastActive = getLastActiveViewManager();
197                if (lastActive == null)
198                    return;
199    
200                lastActive.setLastActive(false);
201    
202                previousVMs.pop();
203    
204                // if there are no more VMs, make sure the IDV code knows about it
205                // by setting the last active VM to null.
206                if (previousVMs.isEmpty()) {
207                    super.setLastActiveViewManager(null);
208                    return;
209                }
210    
211                lastActive = previousVMs.peek();
212                lastActive.setLastActive(true);
213    
214                focusLayerControlsOn(lastActive, false);
215            }
216    
217            inspectStack(debugMsg);
218            super.setLastActiveViewManager(previousVMs.peek());
219    
220            // start active tab testing
221            ComponentHolder holder = 
222                uiManager.getViewManagerHolder(previousVMs.peek());
223            if ((holder != null) && (holder instanceof McvComponentHolder)) {
224                ((McvComponentHolder)holder).setAsActiveTab();
225            }
226            // stop active tab testing
227        }
228    
229        /**
230         * <p>Overwrite the stack containing the ordering of previously active 
231         * ViewManagers.</p>
232         * 
233         * <p>Use this if you want to mess with the user's mind a little bit.</p>
234         * 
235         * @param newOrder The stack containing the new ordering of ViewManagers.
236         */
237        public void setViewManagerOrder(Stack<ViewManager> newOrder) {
238            previousVMs.clear();
239            previousVMs.addAll(newOrder);
240        }
241    
242        public int getComponentHolderCount() {
243            return -1;
244        }
245    
246        public int getComponentGroupCount() {
247            // should be the same as the number of windows (or perhaps numWindows-1).
248            return -1;
249        }
250    
251        /**
252         * Sets the active tab of the dashboard to the layer controls and makes the
253         * first layer (TODO: fix that!) of the given ViewManager the active layer.
254         * 
255         * @param vm The ViewManager to make active.
256         * @param doShow Whether or not the layer controls should become the active
257         *               tab in the dashboard.
258         */
259        private void focusLayerControlsOn(ViewManager vm, boolean doShow) {
260            List<DisplayControlImpl> controls = vm.getControlsForLegend();
261            if (controls != null && !controls.isEmpty()) {
262                DisplayControlImpl control = controls.get(0);
263                if (doShow) {
264                    GuiUtils.showComponentInTabs(control.getOuterContents(), false);
265                }
266            }
267        }
268    
269        /**
270         * Helper method that'll display the ordering of the stack and a helpful
271         * debug message!
272         */
273        private void inspectStack(String msg) {
274            if (!DEBUG) {
275                return;
276            }
277            StringBuilder sb = new StringBuilder(this.hashCode()).append(": ").append(msg).append(": [");
278            for (ViewManager vm : previousVMs) {
279                sb.append(vm.hashCode()).append(',');
280            }
281            logger.trace(sb.append("] Size=").append(previousVMs.size()).toString());
282        }
283    
284        /**
285         * Turns off layer visibility animation for all {@code ViewManager}s. This
286         * is typically only useful for when the user has removed all layers 
287         * <i>without</i> turning off the layer animation setting.
288         */
289        protected void disableAllLayerVizAnimations() {
290            for (ViewManager vm : getViewManagers()) {
291                vm.setAnimatedVisibilityCheckBox(false);
292            }
293        }
294    }