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