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 }