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