/******************************************************************************* * Mission Control Technologies, Copyright (c) 2009-2012, United States Government * as represented by the Administrator of the National Aeronautics and Space * Administration. All rights reserved. * * The MCT platform is licensed under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0. * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. * * MCT includes source code licensed under additional open source licenses. See * the MCT Open Source Licenses file included with this distribution or the About * MCT Licenses dialog available at runtime from the MCT Help menu for additional * information. *******************************************************************************/ package gov.nasa.arc.mct.gui.impl; import gov.nasa.arc.mct.components.AbstractComponent; import gov.nasa.arc.mct.gui.OptionBox; import gov.nasa.arc.mct.gui.View; import gov.nasa.arc.mct.gui.housing.MCTAbstractHousing; import gov.nasa.arc.mct.gui.housing.MCTHousing; import gov.nasa.arc.mct.gui.housing.MCTHousingFactory; import gov.nasa.arc.mct.gui.housing.MCTStandardHousing; import gov.nasa.arc.mct.gui.housing.StatusBarContentProvider; import gov.nasa.arc.mct.gui.housing.registry.UserEnvironmentRegistry; import gov.nasa.arc.mct.gui.menu.MenuFactory; import gov.nasa.arc.mct.gui.util.GUIUtil; import gov.nasa.arc.mct.platform.RootComponent; import gov.nasa.arc.mct.platform.spi.WindowManager; import gov.nasa.arc.mct.services.component.ViewInfo; import gov.nasa.arc.mct.services.component.ViewType; import gov.nasa.arc.mct.util.logging.MCTLogger; import java.awt.Component; import java.awt.GraphicsConfiguration; import java.awt.GraphicsEnvironment; import java.awt.Rectangle; import java.awt.Window; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Implements a window manager. This is the platform default window manager. It * is not designed to be subclassed by component developers. Uses a singleton * pattern to provide only a single, shared instance. */ public class WindowManagerImpl implements WindowManager { private static final Logger logger = LoggerFactory.getLogger(WindowManagerImpl.class); private static final WindowManagerImpl INSTANCE = new WindowManagerImpl(); // Scaling factors used to size child window relative to parent window. static final double LEAF_HORIZONTAL_SCALE = 0.5; static final double LEAF_VERTICAL_SCALE = 0.5; static final double NON_LEAF_HORIZONTAL_SCALE = 0.7; static final double NON_LEAF_VERTICAL_SCALE = 0.7; private static final double MAX_SCALE_FACTOR = 0.85; private static Icon mctIcon = new ImageIcon(WindowManagerImpl.class.getResource("/images/mcticon.png")); // Hints for showInputDialog public static final String PARENT_COMPONENT = "PARENT_COMPONENT"; public static final String OPTION_TYPE = "OPTION_TYPE"; public static final String MESSAGE_TYPE = "MESSAGE_TYPE"; public static final String MESSAGE_OBJECT = "MESSAGE_OBJECT"; /** * Creates a new instance of the window manager. Protected, to enforce the * singleton pattern. */ protected WindowManagerImpl() { } /** * Gets the singleton instance of the window manager. * * @return the window manager */ public static WindowManagerImpl getInstance() { return INSTANCE; } @Override public void openInNewWindow(AbstractComponent component) { GraphicsEnvironment graphicsEnvironment = GraphicsEnvironment.getLocalGraphicsEnvironment(); openInNewWindow(component, graphicsEnvironment.getDefaultScreenDevice().getDefaultConfiguration()); } // For Multiple Monitor Support with GraphicsConfiguration @Override public void openInNewWindow(AbstractComponent component, GraphicsConfiguration graphicsConfig) { assert component != null : "component should not be null"; // we do not know which window this should be relative to, so find the // active window in the current // set of windows Window[] allWindows = Window.getWindows(); assert allWindows != null; Window activeWindow = null; for (Window window : allWindows) { if (window.isActive()) { activeWindow = window; break; } } Set<ViewInfo> views = component.getViewInfos(ViewType.NODE); if (views.isEmpty()) { MCTLogger.getLogger(getClass()).warn("component " + component.getId() + "does not have a Node View"); return; } ViewInfo nodeView = views.iterator().next(); // Determine the desired horizontal/vertical scaling of the new window. double horizontalScale = 0; double verticalScale = 0; if (component.isLeaf()) { horizontalScale = LEAF_HORIZONTAL_SCALE; verticalScale = LEAF_VERTICAL_SCALE; } else { // !isLeaf() horizontalScale = NON_LEAF_HORIZONTAL_SCALE; verticalScale = NON_LEAF_VERTICAL_SCALE; } Window newActiveWindowWithGraphicsConfig = new Window(activeWindow, graphicsConfig); openInWindow(component.getDisplayName(), nodeView, newActiveWindowWithGraphicsConfig, horizontalScale, verticalScale, component); } /** * Opens a component into a new window. * * @param displayName * the display name for the new housing * @param nodeView * the node view role of the component we will open in the new * housing * @param activeWindow * the current active window * @param horizontalScale * the desired horizontal scaling of the window * @param verticalScale * the desired vertical scaling of the window * @param component * the component to open in the window */ protected void openInWindow(String displayName, ViewInfo nodeView, Window activeWindow, double horizontalScale, double verticalScale, AbstractComponent component) { MCTHousing housing = null; if (component instanceof RootComponent) { // THIS Object menu open new user environment housing = MCTHousingFactory.newUserEnvironment(); } else if (component.isLeaf()) { Set<ViewInfo> possibleViews = new LinkedHashSet<ViewInfo>(component.getViewInfos(ViewType.CENTER)); possibleViews.addAll(component.getViewInfos(ViewType.OBJECT)); housing = MCTHousingFactory.newHousing(displayName, (byte) (MCTHousingFactory.CONTROL_AREA_ENABLE | MCTHousingFactory.CONTENT_AREA_ENABLE | MCTHousingFactory.STATUS_AREA_ENABLE), JFrame.DO_NOTHING_ON_CLOSE, possibleViews.iterator().next().createView(component), horizontalScale, verticalScale, activeWindow); } else { housing = MCTHousingFactory.newHousing(displayName, MCTHousingFactory.ENABLE_ALL_AREA, JFrame.DO_NOTHING_ON_CLOSE, GUIUtil.cloneTreeNode(component, nodeView), false, horizontalScale, verticalScale, activeWindow); } ((MCTAbstractHousing) housing).setJMenuBar(MenuFactory.createStandardHousingMenuBar((MCTStandardHousing) housing)); new StatusBarContentProvider(housing); if (housing.getContentArea() != null && !housing.getContentArea().isAreaEmpty()) { // use preferred size since the content area is going to be the dominate focus MCTAbstractHousing abstractHousing = ((MCTAbstractHousing) housing); abstractHousing.pack(); Rectangle maximumWindowBounds = abstractHousing.getGraphicsConfiguration() != null ? abstractHousing.getGraphicsConfiguration().getBounds() : GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds(); abstractHousing.setSize(Math.min((int) (maximumWindowBounds.width*MAX_SCALE_FACTOR), abstractHousing.getSize().width), Math.min((int) (maximumWindowBounds.height*MAX_SCALE_FACTOR), abstractHousing.getSize().height)); housing.getContentArea().getHousedViewManifestation().requestFocusInWindow(); } if (activeWindow != null) { Rectangle activeWindowGraphicsConfigBound = activeWindow.getGraphicsConfiguration().getBounds(); ((MCTAbstractHousing) housing).setLocation(activeWindowGraphicsConfigBound.x, activeWindowGraphicsConfigBound.y); } else { logger.warn("ActiveWindow is NULL because it's not detected during 1st time MCT window opening."); } ((MCTAbstractHousing) housing).setVisible(true); } @Override public AbstractComponent getWindowRootComponent(Component component) { MCTHousing housing = (MCTHousing) SwingUtilities.getAncestorOfClass(MCTHousing.class, component); if (housing != null) { return housing.getWindowComponent(); } return null; } @Override public View getWindowRootManifestation(Component component) { MCTHousing housing = (MCTHousing) SwingUtilities.getAncestorOfClass(MCTHousing.class, component); if (housing != null) { return housing.getHousedViewManifestation(); } return null; } /** * Return the active windows, those which currently can be displayed (have a graphics context). This ensures windows which * have been disposed but not yet garbage collected will not be returned. This prevents the problem * where dispose has been called but the Garbage Collector has not been run so the window is returned * from Window.getWindows(). This causes problems where algorithms assume that Windows.getWindows only returns * windows which are really visible. * @return */ private Window[] getActiveWindows() { List<Window> activeWindows = new ArrayList<Window>(Arrays.asList(Window.getWindows())); Iterator<Window> it = activeWindows.iterator(); while (it.hasNext()) { Window w = it.next(); boolean hasWindowBeenDisposed = w.getGraphics() == null; // this is true if dispose has been called as the // graphics context has been removed if (hasWindowBeenDisposed) { it.remove(); } } return activeWindows.toArray(new Window[activeWindows.size()]); } @Override public void refreshWindows() { Window[] windows = getActiveWindows(); for (Window window : windows) { if (MCTHousing.class.isAssignableFrom(window.getClass())) { MCTHousing.class.cast(window).reloadHousedContent(); } else { window.dispose(); } } } @Override public void closeWindows(String componentId) { Window[] windows = getActiveWindows(); for (Window window : windows) { if (MCTAbstractHousing.class.isAssignableFrom(window.getClass())) { MCTAbstractHousing housing = MCTAbstractHousing.class.cast(window); View housedManifestation = housing.getHousedViewManifestation(); if (housedManifestation != null && componentId.equals(housedManifestation.getManifestedComponent().getId())) { UserEnvironmentRegistry.removeHousing(housing); housing.dispose(); } } } } @SuppressWarnings("unchecked") @Override public <T> T showInputDialog(String title, String message, T[] options, T defaultOption, Map<String, Object> hints) { // Consider platform-specific behavior if (hints != null) { // Parent swing component (for modal-style dialogs) Component parentComponent = null; Object parent = hints.get(PARENT_COMPONENT); if (parent != null && parent instanceof Component) { parentComponent = (Component) parent; } // Options indicator (OptionBox.YES_NO_OPTION, for example) Integer optionType = null; Object optionObj = hints.get(OPTION_TYPE); if (optionObj != null && optionObj instanceof Integer) { optionType = (Integer) optionObj; } // Message type (OptionBox.YES_NO_OPTION, for example) Integer messageType = null; Object messageObj = hints.get(MESSAGE_TYPE); if (messageObj != null && messageObj instanceof Integer) { messageType = (Integer) messageObj; } // Custom message object (a component, for example) Object customMessage = hints.get(MESSAGE_OBJECT); // Only use OptionBox if some known hint has been set if (parentComponent != null || optionType != null || messageType != null || customMessage != null) { int answer = OptionBox.showOptionDialog(parentComponent, customMessage != null ? customMessage : message, title, optionType != null ? optionType.intValue() : OptionBox.OK_CANCEL_OPTION, messageType != null ? messageType.intValue() : OptionBox.INFORMATION_MESSAGE, messageType == null ? mctIcon : null, // Let icon be chosen by Swing, IF message type is set options, defaultOption); return answer >= 0 && answer < options.length ? options[answer] : null; } } // Otherwise, fall back to generic dialog return (T) JOptionPane.showInputDialog(null, message, title, JOptionPane.QUESTION_MESSAGE, mctIcon, options, defaultOption); } }