001/* 002 * This file is part of McIDAS-V 003 * 004 * Copyright 2007-2025 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 https://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 * Create a ... manager of {@link ViewManager ViewManagers}. 088 * 089 * @param idv Reference to the application session. 090 * Cannot be {@code null}. 091 */ 092 public ViewManagerManager(IntegratedDataViewer idv) { 093 super(idv); 094 uiManager = (UIManager)getIdvUIManager(); 095 } 096 097 public int getViewManagerCount() { 098 return getViewManagers().size(); 099 } 100 101 /** 102 * Add the new view manager into the list if we don't have 103 * one with the {@link ViewDescriptor} of the new view manager 104 * already. 105 * 106 * @param newViewManager The new view manager 107 */ 108 @Override public void addViewManager(ViewManager newViewManager) { 109 super.addViewManager(newViewManager); 110 focusLayerControlsOn(newViewManager, false); 111 } 112 113 /** 114 * @return Reference to the stack of previously active ViewManagers. 115 */ 116 public Stack<ViewManager> getViewManagerOrder() { 117 return previousVMs; 118 } 119 120 /** 121 * Overridden so that McV can set the active ViewManager even if there is 122 * only one ViewManager. This is just a UI nicety; it'll allow the McV UI 123 * to show the active ViewManager no matter what. 124 * 125 * @return Always returns true. 126 */ 127 @Override public boolean haveMoreThanOneMainViewManager() { 128 return true; 129 } 130 131 /** 132 * Handles the removal of a ViewManager. McV needs to override this so that 133 * the stack of previously active ViewManagers is ordered properly. McV uses 134 * this method to make the ViewPanel respond immediately to the change. 135 * 136 * @param viewManager The ViewManager being removed. 137 */ 138 @Override public void removeViewManager(ViewManager viewManager) { 139 // the ordering of the stack must be preserved! this is the only chance 140 // to ensure the ordering if the incoming VM is inactive. 141 if (getLastActiveViewManager() != viewManager) { 142 previousVMs.remove(viewManager); 143 inspectStack("removing inactive vm"); 144 } 145 146 // now just sit back and let the IDV and setLastActiveViewManager work 147 // their magic. 148 super.removeViewManager(viewManager); 149 150 // inform UIManager that the VM needs to be dissociated from its 151 // ComponentHolder. 152 uiManager.removeViewManagerHolder(viewManager); 153 154 // force the layer controls tabs to layout the remaining components, 155 // but we don't want to bring it to the front! 156 uiManager.getViewPanel().getContents().validate(); 157 } 158 159 /** 160 * <p>This method is a bit strange. If the given ViewManager is null, then 161 * the IDV has removed the active ViewManager. McV will use the stack of 162 * last active ViewManagers to make the last active ViewManager active once 163 * again.</p> 164 * 165 * <p>If the given ViewManager is not null, but cannot be found in the stack 166 * of previously active ViewManagers, the IDV has created a new ViewManager 167 * and McV must push it on the stack.</p> 168 * 169 * <p>If the given ViewManager is not null and has been found in the stack, 170 * then the user has selected an inactive ViewManager. McV must remove the 171 * ViewManager from the stack and then push it back on top.</p> 172 * 173 * <p>These steps allow McV to make the behavior of closing tabs a bit more 174 * user-friendly. The user is always returned to whichever ViewManager was 175 * last active.</p> 176 * 177 * @param vm See above. :( 178 */ 179 // TODO: when you start removing the debug stuff, just convert the messages 180 // to comments. 181 @Override public void setLastActiveViewManager(ViewManager vm) { 182 String debugMsg = "created new vm"; 183 if (vm != null) { 184 if (previousVMs.search(vm) >= 0) { 185 debugMsg = "reset active vm"; 186 previousVMs.remove(vm); 187 focusLayerControlsOn(vm, false); 188 } 189 previousVMs.push(vm); 190 } else { 191 debugMsg = "removed active vm"; 192 193 ViewManager lastActive = getLastActiveViewManager(); 194 if (lastActive == null) 195 return; 196 197 lastActive.setLastActive(false); 198 199 previousVMs.pop(); 200 201 // if there are no more VMs, make sure the IDV code knows about it 202 // by setting the last active VM to null. 203 if (previousVMs.isEmpty()) { 204 super.setLastActiveViewManager(null); 205 return; 206 } 207 208 lastActive = previousVMs.peek(); 209 lastActive.setLastActive(true); 210 211 focusLayerControlsOn(lastActive, false); 212 } 213 214 inspectStack(debugMsg); 215 super.setLastActiveViewManager(previousVMs.peek()); 216 217 // start active tab testing 218 ComponentHolder holder = 219 uiManager.getViewManagerHolder(previousVMs.peek()); 220 if ((holder != null) && (holder instanceof McvComponentHolder)) { 221 ((McvComponentHolder)holder).setAsActiveTab(); 222 } 223 // stop active tab testing 224 } 225 226 /** 227 * <p>Overwrite the stack containing the ordering of previously active 228 * ViewManagers.</p> 229 * 230 * <p>Use this if you want to mess with the user's mind a little bit.</p> 231 * 232 * @param newOrder The stack containing the new ordering of ViewManagers. 233 */ 234 public void setViewManagerOrder(Stack<ViewManager> newOrder) { 235 previousVMs.clear(); 236 previousVMs.addAll(newOrder); 237 } 238 239 public int getComponentHolderCount() { 240 return -1; 241 } 242 243 public int getComponentGroupCount() { 244 // should be the same as the number of windows (or perhaps numWindows-1). 245 return -1; 246 } 247 248 /** 249 * Sets the active tab of the dashboard to the layer controls and makes the 250 * first layer (TODO: fix that!) of the given ViewManager the active layer. 251 * 252 * @param vm The ViewManager to make active. 253 * @param doShow Whether or not the layer controls should become the active 254 * tab in the dashboard. 255 */ 256 private void focusLayerControlsOn(ViewManager vm, boolean doShow) { 257 List<DisplayControlImpl> controls = vm.getControlsForLegend(); 258 if (controls != null && !controls.isEmpty()) { 259 DisplayControlImpl control = controls.get(0); 260 if (doShow) { 261 GuiUtils.showComponentInTabs(control.getOuterContents(), false); 262 } 263 } 264 } 265 266 /** 267 * Helper method that'll display the ordering of the stack and a helpful 268 * debug message! 269 * 270 * @param msg Message to include in output. 271 */ 272 private void inspectStack(String msg) { 273 if (!DEBUG) { 274 return; 275 } 276 StringBuilder sb = new StringBuilder(this.hashCode()).append(": ").append(msg).append(": ["); 277 for (ViewManager vm : previousVMs) { 278 sb.append(vm.hashCode()).append(','); 279 } 280 logger.trace(sb.append("] Size=").append(previousVMs.size()).toString()); 281 } 282 283 /** 284 * Turns off layer visibility animation for all {@code ViewManager}s. This 285 * is typically only useful for when the user has removed all layers 286 * <i>without</i> turning off the layer animation setting. 287 */ 288 protected void disableAllLayerVizAnimations() { 289 for (ViewManager vm : getViewManagers()) { 290 vm.setAnimatedVisibilityCheckBox(false); 291 } 292 } 293}