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