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 }